Artisan Command Signatures and Interactive Prompts with Laravel Prompts
A command with a required argument that gets run without it usually just throws a "Missing required argument" error and stops. Laravel Prompts, bundled into the framework, turns that dead end into an interactive question — and it's already wired into the $signature syntax you're using today.
The baseline: signature arguments and options
protected $signature = 'reports:generate {team} {--format=pdf} {--email? : Send the report by email}';
{team}— required argument{--format=pdf}— option with a default value{--email?}— optional flag, with an inline description shown inphp artisan help reports:generate
Run php artisan reports:generate with no team argument, and Laravel Prompts steps in automatically: if the argument has no default and isn't provided, Laravel prompts for it interactively using the argument's name as the question, rather than erroring immediately. This happens for free — no extra code needed beyond the signature itself.
Customizing the automatic prompt
Override promptForMissingArgumentsUsing() to control exactly what's asked and how it's validated:
namespace App\Console\Commands;
use App\Models\Team;
use Illuminate\Console\Command;
use function Laravel\Prompts\search;
class GenerateReport extends Command
{
protected $signature = 'reports:generate {team}';
protected function promptForMissingArgumentsUsing(): array
{
return [
'team' => fn () => search(
label: 'Which team is this report for?',
options: fn ($value) => Team::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all(),
),
];
}
public function handle(): void
{
$this->info("Generating report for team {$this->argument('team')}...");
}
}
Instead of a plain text prompt, missing the team argument now triggers a searchable list backed by a live database query — genuinely useful when "team" is an ID a human wouldn't know to type from memory.
Beyond missing arguments: prompts as UI
Laravel Prompts is a standalone toolkit of terminal UI components, and it's worth using deliberately inside handle(), not just as the missing-argument fallback:
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\select;
use function Laravel\Prompts\progress;
public function handle(): void
{
$format = select(
label: 'Report format?',
options: ['pdf' => 'PDF', 'csv' => 'CSV', 'xlsx' => 'Excel'],
default: 'pdf',
);
if (! confirm('This will email the report to the whole team. Continue?')) {
$this->info('Cancelled.');
return;
}
$teams = Team::all();
progress(
label: 'Generating reports',
steps: $teams,
callback: fn (Team $team) => $this->generateFor($team, $format),
);
}
progress() renders a real progress bar tied to the actual collection being iterated, rather than a manual $this->output->progressStart()/progressAdvance() dance.
Respecting non-interactive environments
Every prompt function checks whether the command is running in a TTY. In CI, a cron job, or any context piped through php artisan reports:generate --no-interaction, prompts are skipped and fall back to defaults or throw if a required value genuinely has none — meaning the same command works identically for a human running it locally and a scheduled job running it unattended, without separate code paths. That's the real payoff: you write one command, and it behaves like a friendly wizard for a human and a silent, deterministic script for automation, based entirely on how it's invoked.