Skip to content

Laravel Artisan Console — Custom Commands and CLI Development

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Laravel Artisan Console. We cover key concepts, practical examples, and best practices to help you master this topic.

Laravel Artisan provides a command-line interface with custom commands supporting input arguments, options, progress bars, and scheduled task execution.

What You'll Learn

By the end of this tutorial, you'll create custom Artisan commands, handle input arguments and options, implement command logic with progress bars, and schedule commands.

Why Artisan Matters

Artisan commands automate repetitive tasks like data imports, report generation, and maintenance operations. Scheduled commands replace Cron Jobs with Laravel-managed tasks.

Real-World Use

A daily Artisan command imports orders from an external API, processes them, and sends a summary email. Another command generates invoice PDFs for the billing team.

Artisan Path

flowchart LR
  A[Laravel Framework] --> B[Artisan Console]
  B --> C[Commands]
  B --> D[Scheduling]
  B --> E[Input/Output]
  B --> F[Progress Bars]
  B --> G{You Are Here}
  style G fill:#f90,color:#fff

Creating a Command

Generate and implement a custom Artisan command.

php artisan make:command ImportOrders
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Services\OrderImporter;
class ImportOrders extends Command {
    protected $signature = "orders:import
        {--source= : The data source URL}
        {--limit=100 : Maximum orders to import}
        {--force : Skip confirmation prompt}
        {source? : Optional positional argument}";
    protected $description = "Import orders from external API";
    public function handle(OrderImporter $importer): int {
        $this->info("Starting order import...");
        $source = $this->option("source") ?? config("services.orders.api_url");
        $limit = (int) $this->option("limit");
        if (!$this->option("force") && !$this->confirm("Import from $source?")) {
            $this->warn("Import cancelled.");
            return Command::FAILURE;
        }
        $count = $importer->import($source, $limit);
        $this->info("Imported $count orders successfully.");
        return Command::SUCCESS;
    }
}

Progress Bars

Show progress for long-running tasks.

<?php
public function handle(): int {
    $users = User::where("needs_update", true)->get();
    $bar = $this->output->createProgressBar(count($users));
    $bar->start();
    foreach ($users as $user) {
        $user->updateFromApi();
        $bar->advance();
    }
    $bar->finish();
    $this->newLine();
    $this->info("Updated " . count($users) . " users.");
    return Command::SUCCESS;
}

Command Scheduling

Schedule commands in the Kernel.

<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel {
    protected function schedule(Schedule $schedule): void {
        $schedule->command("orders:import --force")
            ->dailyAt("02:00")
            ->withoutOverlapping()
            ->runInBackground()
            ->onFailure(function () {
                Log::error("Order import failed");
            });
        $schedule->command("reports:generate")
            ->weeklyOn(1, "06:00")
            ->emailOutputTo("admin@example.com");
        $schedule->command("cache:clean")
            ->hourly()
            ->environments(["production"]);
    }
}

Command Testing

Test Artisan commands with unit tests.

<?php
namespace Tests\Feature\Console;
use Tests\TestCase;
class ImportOrdersTest extends TestCase {
    public function test_import_command_runs_successfully(): void {
        $this->artisan("orders:import", ["--source" => "https://api.test.com/orders", "--force" => true])
            ->expectsOutput("Starting order import...")
            ->assertExitCode(0);
    }
    public function test_import_requires_confirmation(): void {
        $this->artisan("orders:import")
            ->expectsQuestion("Import from https://api.example.com/orders?", "no")
            ->expectsOutput("Import cancelled.")
            ->assertExitCode(1);
    }
}

Common Mistakes

1. Heavy Logic in handle() Method

Command handlers should call services. Business logic in commands is untestable.

2. Not Returning Exit Codes

Commands must return Command::SUCCESS or Command::FAILURE for scheduling to work.

3. Ignoring Command Failure Events

Use onFailure() in scheduling to handle failures gracefully.

4. Overlapping Commands

Without withoutOverlapping(), commands scheduled to run frequently may overlap.

5. Hardcoding Configuration in Commands

Use config() or environment variables for configurable values.

Practice Questions

1. How do you define command arguments?

In the signature using curly braces: {argument} for required, {argument?} for optional.

2. What does withoutOverlapping() do?

Prevents the command from running if a previous instance is still running.

3. How do you return a failure from a command?

Return Command::FAILURE or exit code 1.

4. How do you display a progress bar?

Use $this->output->createProgressBar($total) and call advance() in the loop.

5. Challenge: Create a command that exports data to CSV with progress.

<?php
class ExportUsers extends Command {
    protected $signature = "users:export {--file=users.csv}";
    public function handle(): int {
        $users = User::all();
        $file = fopen($this->option("file"), "w");
        $bar = $this->output->createProgressBar(count($users));
        foreach ($users as $user) {
            fputcsv($file, [$user->id, $user->name, $user->email]);
            $bar->advance();
        }
        $bar->finish();
        $this->info("Exported " . count($users) . " users.");
        return Command::SUCCESS;
    }
}

FAQ

Can Artisan commands call other commands?

Yes. Use $this->call('other:command') or $this->callSilent().

How do I pass data between commands?

Use options and arguments on the command line.

What is the difference between schedule and cron?

Schedule is defined in code and runs via php artisan schedule:run from a single cron entry.

Can I run commands in maintenance mode?

Yes, unless the command explicitly checks for maintenance mode.

How do I test scheduled commands?

Test the command itself. Schedule testing is covered by Laravel's schedule tests.

Mini Project: Data Export Command Suite

Build a suite of export commands with scheduling.

<?php
class DailyReport extends Command {
    protected $signature = "report:daily {--format=csv}";
    public function handle(): int {
        $this->info("Generating daily report...");
        $this->call("users:export", ["--file" => storage_path("reports/users.csv")]);
        $this->call("orders:export", ["--file" => storage_path("reports/orders.csv")]);
        $this->info("Reports generated successfully.");
        return Command::SUCCESS;
    }
}

What's Next

Laravel Queues Deep Laravel Horizon Laravel Broadcasting

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro