Laravel Artisan is Laravel's built-in command-line interface (CLI) and it's very powerful. Besides the hundreds of built-in commands like php artisan migrate or php artisan make:model, you can also create your own commands. Custom Artisan commands are very useful for automating routine tasks such as cleaning up old data, sending daily reports, or syncing data from an external API.
Creating a New Command
Use Artisan to generate a new command skeleton:
php artisan make:command SendDailyReport
The file is created at app/Console/Commands/SendDailyReport.php. Laravel 11 automatically discovers commands in this folder, so no manual registration is needed.
The Anatomy of a Laravel Command
Open the newly created file and understand its structure:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class SendDailyReport extends Command
{
// Signature: the command name and its arguments/options
protected $signature = 'report:daily
{type : Report type (sales/inventory)}
{--email= : Destination email address}
{--dry-run : Run without actually sending the email}';
// Description shown in php artisan list
protected $description = 'Send a daily report to the admin via email';
// Main command logic
public function handle(): int
{
$type = $this->argument('type');
$email = $this->option('email') ?? 'admin@example.com';
$dryRun = $this->option('dry-run');
$this->info("Preparing report of type: {$type}");
if (! in_array($type, ['sales', 'inventory'])) {
$this->error("Invalid report type. Use 'sales' or 'inventory'.");
return Command::FAILURE;
}
// Simulate generating the report
$this->withProgressBar(range(1, 5), function ($step) {
sleep(1); // Simulate a process
});
$this->newLine();
if ($dryRun) {
$this->warn("Dry-run mode: email not sent to {$email}");
} else {
$this->info("The {$type} report was sent successfully to: {$email}");
// Here you would call a Mailable or Notification
}
return Command::SUCCESS;
}
}
Arguments and Options in the Signature
The signature format follows these rules:
{name}— required argument.{name?}— optional argument.{name=default}— argument with a default value.{--option}— boolean flag (present/absent).{--option=}— option with a string value.{--option=default}— option with a default value.
Running the Command
# Required argument
php artisan report:daily sales
# With an option
php artisan report:daily inventory --email=budi@example.com
# With the dry-run flag
php artisan report:daily sales --dry-run
Interacting with the User
Laravel commands provide various methods for interacting with the user in the terminal:
public function handle(): int
{
// Ask for confirmation
if (! $this->confirm('Are you sure you want to delete old data?')) {
$this->line('Operation cancelled.');
return Command::SUCCESS;
}
// Ask for input from the user
$name = $this->ask('Enter the report name:');
// Choice from a list
$format = $this->choice('Output format:', ['PDF', 'Excel', 'CSV'], 0);
// Ask for a password (hidden)
$secret = $this->secret('Enter the API key:');
// Colored output
$this->info('Info (green)');
$this->warn('Warning (yellow)');
$this->error('Error (red)');
// Display a table
$this->table(
['ID', 'Name', 'Email'],
User::select('id', 'name', 'email')->limit(5)->get()->toArray()
);
return Command::SUCCESS;
}
Real Example: A Command to Delete Old Data
<?php
namespace App\Console\Commands;
use App\Models\Log;
use Illuminate\Console\Command;
class PruneOldLogs extends Command
{
protected $signature = 'logs:prune {--days=30 : Delete logs older than N days}';
protected $description = 'Delete old logs from the database';
public function handle(): int
{
$days = (int) $this->option('days');
$cutoff = now()->subDays($days);
$count = Log::where('created_at', '<', $cutoff)->count();
if ($count === 0) {
$this->info('No old logs to delete.');
return Command::SUCCESS;
}
if ($this->confirm("This will delete {$count} logs before {$cutoff->toDateString()}. Continue?")) {
Log::where('created_at', '<', $cutoff)->delete();
$this->info("{$count} logs deleted successfully.");
}
return Command::SUCCESS;
}
}
Scheduling a Command with the Scheduler
To run a command automatically on a schedule, register it in routes/console.php (Laravel 11) or app/Console/Kernel.php (Laravel 10):
// Laravel 11 — routes/console.php
use Illuminate\Support\Facades\Schedule;
Schedule::command('report:daily sales --email=admin@site.com')->dailyAt('07:00');
Schedule::command('logs:prune --days=30')->weekly();
// Add a cron entry on the server:
// * * * * * cd /path-to-project && php artisan schedule:run >> /dev/null 2>&1
Conclusion
Creating a custom Artisan command in Laravel is the best way to automate routine tasks that don't need to be done manually. With arguments, options, rich terminal interaction, and scheduler integration, Laravel commands can become the backbone of your application's background processes. From simple commands like clearing the cache to complex ones like generating PDF reports — it can all be done elegantly with Artisan.