Skip to main content

Command

php artisan make:command {name} --module={module}
Create a new Artisan command class in your module’s src/Console/Commands directory.

Parameters

name
string
required
The name of the command classConventions:
  • Use PascalCase
  • Use descriptive action names
Examples:
  • PublishPost
  • ImportPosts
  • SendWeeklyDigest

Options

--module
string
required
The name of the module where the command should be createdExample: --module=blog
--command
string
The terminal command that should be assigned
php artisan make:command PublishPost --command=post:publish --module=blog
When using --module, if you don’t specify --command, the command signature will automatically be prefixed with your module name.
--test
boolean
Generate an accompanying test for the command
--pest
boolean
Generate an accompanying Pest test

Examples

Basic Command

php artisan make:command PublishPost --module=blog
Generates a console command:
namespace Modules\Blog\Console\Commands;

use Illuminate\Console\Command;

class PublishPost extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'blog:publish-post';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Command description';

    /**
     * Execute the console command.
     */
    public function handle(): int
    {
        return Command::SUCCESS;
    }
}
Location: app-modules/blog/src/Console/Commands/PublishPost.php
The command signature is automatically set to blog:publish-post based on your module name and class name.

Command with Custom Signature

php artisan make:command ImportPosts --command=posts:import --module=blog
Generates a command with a custom signature:
protected $signature = 'posts:import';

Command with Arguments

After generating, customize the signature to accept arguments:
protected $signature = 'blog:publish-post {postId : The ID of the post to publish}';

public function handle(): int
{
    $postId = $this->argument('postId');
    
    $this->info("Publishing post {$postId}...");
    
    // Your logic here
    
    return Command::SUCCESS;
}

Command with Options

Add options to your command signature:
protected $signature = 'blog:import-posts 
    {--force : Force the import even if posts exist}
    {--limit=100 : Number of posts to import}';

public function handle(): int
{
    $force = $this->option('force');
    $limit = $this->option('limit');
    
    if ($force) {
        $this->warn('Force mode enabled');
    }
    
    $this->info("Importing up to {$limit} posts...");
    
    // Your logic here
    
    return Command::SUCCESS;
}

Interactive Command

Use Laravel’s console helpers for user interaction:
protected $signature = 'blog:create-post';
protected $description = 'Create a new blog post interactively';

public function handle(): int
{
    $title = $this->ask('What is the post title?');
    $publish = $this->confirm('Do you want to publish it now?', true);
    $category = $this->choice('Select a category', ['Tech', 'News', 'Tutorial']);
    
    $this->info('Creating post...');
    
    // Create the post
    
    $this->info('Post created successfully!');
    
    return Command::SUCCESS;
}

Command with Progress Bar

protected $signature = 'blog:sync-posts';
protected $description = 'Sync posts from external API';

public function handle(): int
{
    $posts = $this->fetchPostsFromApi();
    
    $bar = $this->output->createProgressBar(count($posts));
    $bar->start();
    
    foreach ($posts as $post) {
        $this->syncPost($post);
        $bar->advance();
    }
    
    $bar->finish();
    $this->newLine();
    
    $this->info('All posts synced successfully!');
    
    return Command::SUCCESS;
}

Module-Specific Behavior

Automatic Command Naming

When you use the --module option, the command generator automatically prefixes the CLI command with your module name:
MakeCommand.php:13-34
protected function replaceClass($stub, $name)
{
    $module = $this->module();
    
    $stub = parent::replaceClass($stub, $name);
    
    if ($module) {
        $cli_name = Str::of($name)->classBasename()->kebab();
        
        $find = [
            '{{command}}',
            '{{ command }}',
            'dummy:command',
            'command:name',
            "app:{$cli_name}",
        ];
        
        $stub = str_replace($find, "{$module->name}:{$cli_name}", $stub);
    }
    
    return $stub;
}
This ensures commands are namespaced by module:
php artisan make:command PublishPost
# Signature: app:publish-post
Module-namespaced commands prevent naming conflicts when multiple modules have similar functionality.

Custom Command Names

You can override the automatic naming:
php artisan make:command PublishPost --command=custom:publish --module=blog
This sets the signature to custom:publish instead of blog:publish-post.

Command Registration

Automatic Registration

Laravel automatically discovers and registers commands in your module. Just ensure your service provider is registered in composer.json:
composer.json
{
    "extra": {
        "laravel": {
            "providers": [
                "Modules\\Blog\\Providers\\BlogServiceProvider"
            ]
        }
    }
}

Manual Registration

Alternatively, register in your service provider:
BlogServiceProvider.php
use Modules\Blog\Console\Commands\PublishPost;
use Modules\Blog\Console\Commands\ImportPosts;

public function boot(): void
{
    if ($this->app->runningInConsole()) {
        $this->commands([
            PublishPost::class,
            ImportPosts::class,
        ]);
    }
}

Running Your Command

After creating and registering your command:
# Run the command
php artisan blog:publish-post

# View command help
php artisan help blog:publish-post

# List all commands (including module commands)
php artisan list

Common Command Patterns

Command with Dependency Injection

Inject services through the constructor:
use Modules\Blog\Services\PostPublisher;

class PublishPost extends Command
{
    protected $signature = 'blog:publish-post {postId}';
    
    public function __construct(
        private PostPublisher $publisher
    ) {
        parent::__construct();
    }
    
    public function handle(): int
    {
        $postId = $this->argument('postId');
        
        $this->publisher->publish($postId);
        
        return Command::SUCCESS;
    }
}

Command with Database Operations

use Modules\Blog\Models\Post;

class PublishScheduledPosts extends Command
{
    protected $signature = 'blog:publish-scheduled';
    protected $description = 'Publish all scheduled posts';
    
    public function handle(): int
    {
        $posts = Post::where('status', 'scheduled')
            ->where('publish_at', '<=', now())
            ->get();
        
        if ($posts->isEmpty()) {
            $this->info('No posts to publish.');
            return Command::SUCCESS;
        }
        
        foreach ($posts as $post) {
            $post->update(['status' => 'published']);
            $this->line("Published: {$post->title}");
        }
        
        $this->info("Published {$posts->count()} posts.");
        
        return Command::SUCCESS;
    }
}

Command with Error Handling

public function handle(): int
{
    try {
        $this->info('Starting import...');
        
        $this->importPosts();
        
        $this->info('Import completed successfully!');
        return Command::SUCCESS;
        
    } catch (\Exception $e) {
        $this->error('Import failed: ' . $e->getMessage());
        return Command::FAILURE;
    }
}

Scheduled Command

Commands can be scheduled in app/Console/Kernel.php:
Kernel.php
protected function schedule(Schedule $schedule): void
{
    $schedule->command('blog:publish-scheduled')
        ->hourly()
        ->withoutOverlapping();
    
    $schedule->command('blog:send-digest')
        ->weeklyOn(1, '8:00')
        ->emailOutputOnFailure('[email protected]');
}

Output Formatting

Tables

$this->table(
    ['ID', 'Title', 'Status'],
    $posts->map(fn($post) => [
        $post->id,
        $post->title,
        $post->status,
    ])
);

Styled Output

$this->info('Informational message');
$this->comment('Comment message');
$this->question('Question message');
$this->error('Error message');
$this->warn('Warning message');
$this->line('Plain message');
$this->newLine();

Alert Boxes

$this->alert('Important Notice');

Testing Commands

Test your commands using Laravel’s testing helpers:
namespace Modules\Blog\Tests\Console;

use Modules\Blog\Models\Post;
use Tests\TestCase;

class PublishPostTest extends TestCase
{
    public function test_it_publishes_a_post(): void
    {
        $post = Post::factory()->create(['status' => 'draft']);
        
        $this->artisan('blog:publish-post', ['postId' => $post->id])
            ->expectsOutput('Publishing post ' . $post->id)
            ->assertSuccessful();
        
        $this->assertEquals('published', $post->fresh()->status);
    }
    
    public function test_it_handles_invalid_post_id(): void
    {
        $this->artisan('blog:publish-post', ['postId' => 999])
            ->expectsOutput('Post not found')
            ->assertFailed();
    }
}

Best Practices

Make command signatures clear and intuitive:
  • blog:publish-post
  • blog:import-posts
  • blog:do
  • process
Write clear descriptions for the --help output:
protected $description = 'Publish a draft post by ID';
Use descriptive text in your signature:
protected $signature = 'blog:publish-post 
    {postId : The ID of the post to publish}
    {--queue : Queue the publishing job}';
Use Laravel’s command constants:
return Command::SUCCESS;  // 0
return Command::FAILURE;  // 1
return Command::INVALID;  // 2
Always inform users about what’s happening:
$this->info('Starting process...');
// ... do work ...
$this->info('Process completed!');
Catch exceptions and provide meaningful error messages:
try {
    $this->processData();
} catch (\Exception $e) {
    $this->error('Failed: ' . $e->getMessage());
    return Command::FAILURE;
}

See Also

Build docs developers (and LLMs) love