Skip to main content
Laravel Applications

Laravel Prompts v0.3.15: The CLI Primitives You Needed

How to use Laravel Prompts task(), stream(), notify(), autocomplete() and title() to build Artisan commands people trust, with real code and caveats.

9 min
Read time
1,684
Words
Published
Last revised
Engr Mejba Ahmed

Written by

Engr Mejba Ahmed

Share Article

Laravel Prompts v0.3.15: The CLI Primitives You Needed

Laravel finally gave Artisan commands a real output story, and the interesting question is not "what are the new functions." It is "which contexts do they belong in." I ship custom artisan commands in my own production app, some run by humans and some by the scheduler every five minutes, and those two audiences need opposite things from terminal output. The new Laravel Prompts primitives are superb for the first audience and a trap for the second. This post covers both halves.

The release itself: Laravel Prompts v0.3.15 landed in March 2026 with five new primitives, task(), stream(), notify(), autocomplete(), and title(), extracted from the internal CLI the Laravel team built for Laravel Cloud. That origin matters. These are not speculative APIs; they were shaped by engineers watching real deployments scroll by, then generalized for the rest of us. If you are on a current Laravel release you likely already have them; otherwise:

composer require laravel/prompts

Laravel Prompts v0.3.15: The CLI Primitives You Needed - overview of task(): long operations finally get a real ui, notify(): the os taps you on the shoulder

task(): long operations finally get a real UI

task() is the centerpiece. One function call gives you an animated spinner, a scrolling log area, a label you can update as phases change, and status messages that persist instead of scrolling away.

use function Laravel\Prompts\task;

task(
    title: 'Deploying application',
    callback: function ($task) {
        $task->label('Pulling latest code');
        $task->log('Running git pull origin main...');
        // ... work ...

        $task->label('Compiling assets');
        $task->log('Running npm build...');
        // ... work ...

        $task->succeed('Deployment complete');
    }
);

While the callback runs, the spinner animates next to the label, and every $task->log() line flows into a scrolling area beneath it. Old lines push up, recent output stays visible, and the spinner never stops, so the user always knows the process is alive. That last part is the entire psychological difference between "this is working" and "should I Ctrl+C?"

The two-level information design is the clever bit. $task->label() changes the headline ("Pulling code" → "Compiling assets" → "Running migrations") without disturbing the log stream underneath. Phase at a glance, detail on demand.

Status messages that refuse to scroll away

task(
    title: 'Running migrations',
    callback: function ($task) {
        $task->log('42 migrations pending');
        $task->warning('3 tables will be dropped');
        // ... the wall of SQL output scrolls below ...
        $task->succeed('Migrations applied');
    }
);

$task->warning() and $task->error() render as pinned, highlighted lines above the scrolling log. They do not disappear under verbose output. I have hacked around exactly this for years with shouty ==== banners; having "3 tables will be dropped" stay on screen while SQL scrolls past is the feature working as intended.

The pcntl caveat

The animated spinner needs the pcntl PHP extension, present on macOS and virtually every Linux box, absent on native Windows. Without it you get a static fallback: same logs, same statuses, no animation. Check with:

php -m | grep pcntl

Design your output to still be useful un-animated and this never bites you.

notify(): the OS taps you on the shoulder

use function Laravel\Prompts\notify;

notify(
    title: 'Database Seeded',
    body: sprintf('%d records in %ds.', $count, $elapsed)
);

A native desktop notification from a PHP process: Notification Center on macOS, the notification daemon on Linux, bridged notifications under WSL. The use case is any command long enough that you switch windows: big seeds, imports, batch content jobs. Add one line at the end and stop doing the "did that finish yet" terminal check-in dance.

stream(): AI output rendered like it should be

stream() prints text progressively with a fade-in, which sounds cosmetic until you connect it to its real purpose: rendering streamed LLM responses inside artisan commands.

use function Laravel\Prompts\stream;

$stream = stream('AI Analysis');

foreach ($llmResponse as $chunk) {
    $stream->append($chunk->text);
}

$stream->close();

If you are building AI-assisted tooling in Laravel, and after a year of the ecosystem sprinting in that direction, many of us are, this is the missing render primitive. I have been experimenting with the Laravel AI SDK in a real app, and terminal output was the ugliest part of that loop; stream() plus a streaming API response gives you the typewriter effect in your own CLI. Do not forget close(), which restores the cursor and finalizes the output.

autocomplete() vs suggest(): pick by user intent

autocomplete() shows inline ghost text you accept with Tab, the pattern you know from IDEs and browser address bars:

use function Laravel\Prompts\autocomplete;

$env = autocomplete(
    label: 'Deploy to which environment?',
    options: fn (string $value) => collect(['production', 'staging', 'preview'])
        ->filter(fn ($o) => str_contains($o, strtolower($value)))
        ->values()
        ->all()
);

The existing suggest() shows a dropdown of matches instead. The decision rule: suggest() when the user is browsing and needs to see options; autocomplete() when the user already knows the answer and wants fewer keystrokes. For commands experienced developers run daily, environment pickers, branch selectors, autocomplete is noticeably faster.

title(): the terminal tab tells you what it is doing

use function Laravel\Prompts\title;

title('Deploy: production');
// ... work ...
title('Deploy: complete');

It sets the terminal window or tab title. Trivial to implement, disproportionately useful if you live with six terminal tabs: the tab bar becomes a status board. Pair it with notify() and a long-running command respects your attention at both ends: the tab shows state at a glance, the notification pulls you back when it matters.

The lesson from my own commands: know your audience

Here is the part I have not seen anyone else write down, and it comes directly from operating my own tooling.

My site's generate:sitemap command renders three sitemap files and prints one plain line per file: Wrote sitemap.xml (48213 bytes). It runs two ways: occasionally by me, and every five minutes by the scheduler, plus once per deploy from the CI pipeline. When I first read the v0.3.15 release notes my instinct was to dress that command up with task(). I am glad I stopped, because it would have made the command worse.

Scheduled and CI contexts have no human watching. There is no TTY, so there is nobody for a spinner to reassure, and what you want in the log file is exactly what the command produces now: one greppable line per artifact with a byte count, because grep sitemap.xml scheduler.log | tail is how I verified cron was alive during a production incident. Spinners, fades, and scrolling regions are for humans present at the keyboard; logs are for the human who arrives three days later with a question.

So the real decision framework has a step zero that comes before choosing between primitives: who is watching this run?

  • A human, interactively: use the primitives generously. task() for anything multi-phase over a few seconds, spin() for a single short wait, progress() when iterating a known count, notify() if they might tab away.
  • The scheduler, a queue worker, or CI: plain, line-oriented, greppable output. One line per meaningful event, counts and byte sizes included. Your future self debugging an outage is the user.
  • Both, same command: branch on it. $this->output->isDecorated() or a simple --plain flag lets a deploy command be beautiful when run by hand and boring when run by GitHub Actions.

Prompts is polite about non-interactive environments, but "does not break in cron" is a lower bar than "is what you want in cron." Design for the reader of the log, not just the watcher of the terminal.

A rebuilt command, before and after

The before state of most deployment commands, mine included at one point: bursts of $this->info() between 30-second silences. The after state, using everything above:

use function Laravel\Prompts\task;
use function Laravel\Prompts\notify;
use function Laravel\Prompts\title;

public function handle(): int
{
    title('Deploy: production');

    task(
        title: 'Deploying to production',
        callback: function ($task) {
            $task->label('Pulling latest code');
            exec('git pull origin main 2>&1', $out, $code);
            foreach ($out as $line) {
                $task->log($line);
            }

            if ($code !== 0) {
                $task->error('Git pull failed — aborting');
                return;
            }

            $task->label('Installing dependencies');
            exec('composer install --no-dev 2>&1', $composerOut);

            $task->label('Running migrations');
            Artisan::call('migrate', ['--force' => true]);
            $task->log('Migrations applied.');

            $task->label('Restarting queue workers');
            Artisan::call('queue:restart');

            $task->succeed('Deployment complete');
        }
    );

    title('Deploy: complete');
    notify(title: 'Deploy finished', body: 'Production is live.');

    return self::SUCCESS;
}

Note the queue:restart phase: if your app runs supervised queue workers, a deploy that skips it leaves old code running in memory, which is its own quiet disaster; the full worker setup is in my Supervisor on Amazon Linux 2023 guide.

Why this release matters beyond the functions

Prompts launched in 2023 focused on input: select(), confirm(), text(), polished ways to ask. The output side stayed primitive, info() lines and a progress bar, which is why long PHP commands felt a decade behind Node or Rust CLI tooling. v0.3.15 completes the pair. Input and output are both first-class now, and a PHP CLI can genuinely feel like a modern tool instead of a script that happens to print.

If your artisan commands are part of an AI-driven workflow, agents running them, humans reviewing, this polish compounds; well-structured command output is also easier for a coding agent to parse when it runs your tooling, something I lean on constantly in the terminal stack I keep around Claude Code. And if the agent is the one writing the commands, Laravel Boost's MCP server gives it the application context to do it correctly.

Pick your slowest, most-avoided artisan command and give it thirty minutes: title() at the top, task() around the work, notify() at the end, and plain output preserved for any scheduled path. The command will do exactly what it did before and feel like a different tool.

I build this kind of developer tooling, custom artisan commands, deployment pipelines, CLI UX included, for teams that want their internal tools to feel as finished as their product. If yours could use that treatment, reach out and tell me what your slowest command does; it is usually a smaller project than people expect.

Advertisement
Coffee cup

Enjoyed this article?

Your support helps me create more in-depth technical content, open-source tools, and free resources for the developer community.

Related Topics

Engr Mejba Ahmed

Engr Mejba Ahmed

Engr. Mejba Ahmed builds AI-powered applications and secure cloud systems for businesses worldwide. With 8+ years shipping production software in Laravel, Python, and AWS, he's helped companies automate workflows, reduce infrastructure costs, and scale without security headaches. He writes about practical AI integration, cloud architecture, and developer productivity.

Related Articles

Browse All

Comments

Leave a Comment

Comments are moderated before appearing.

Learning Resources

Expand Your Knowledge

Accelerate your growth with structured courses, verified certificates, interactive flashcards, and production-ready AI agent skills.

Sample Certificate of Completion

Sample certificate — complete any course to earn yours

Engr Mejba Ahmed

Engr Mejba Ahmed

AI assistant · trained on my work

👋

Hey there!

Quick Actions

WhatsApp Direct line to me

Chat on WhatsApp

+880 1723 741224 · Replies within the hour on working days

Popular Questions

Engr Mejba Ahmed is connected
Engr Mejba Ahmed is typing...
Engr Mejba Ahmed avatar

✉ Want me to follow up? Drop your email

Engr Mejba Ahmed avatar

📞 Connect Directly

Choose how you'd like to reach me

WhatsApp

+880 1723 741224

Email

mejba.13@gmail.com

✓ Details sent! I'll get back to you shortly.

Powered by OpenAI

335+

Blog Posts

25

AI Courses

63

Projects

Services & Expertise

Pricing & Process

Learning & Resources

Connect & Support