Command
php artisan make:command {name} --module= { module}
Create a new Artisan command class in your module’s src/Console/Commands directory.
Parameters
The name of the command class Conventions:
Use PascalCase
Use descriptive action names
Examples:
PublishPost
ImportPosts
SendWeeklyDigest
Options
The name of the module where the command should be created Example: --module=blog
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.
Generate an accompanying test for the command
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:
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:
Without --module
With --module=blog
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:
{
"extra" : {
"laravel" : {
"providers" : [
"Modules \\ Blog \\ Providers \\ BlogServiceProvider"
]
}
}
}
Manual Registration
Alternatively, register in your service provider:
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:
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] ' );
}
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
Use descriptive signatures
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' ;
Document arguments and options
Use descriptive text in your signature: protected $signature = 'blog:publish-post
{postId : The ID of the post to publish}
{--queue : Queue the publishing job}' ;
Return appropriate exit codes
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