Laravel Artisan Commands You Should Know
· 4 min read
Across the last several lessons, Artisan has shown up constantly in the background.
Across the last several lessons, Artisan has shown up constantly in the background: php artisan migrate in lesson 10, php artisan make:request in lesson 12, php artisan queue:work in lesson 15. This lesson steps back to properly introduce Artisan itself, Laravel's built-in command-line tool, and shows you how to extend it with commands of your own.
What Artisan Commands Do
Artisan is the single entry point for almost every administrative and generative task in a Laravel project. Run it with no arguments, or with list, to see every available command:
php artisan list
Commands fall into roughly two categories: "make" commands that generate boilerplate files for you, and operational commands that actually do something to your running application. You have already met several make commands across this course: make:migration, make:request, make:rule. The full family is much larger, and worth knowing exists even if you don't memorize every one: make:model, make:controller, make:middleware, make:policy, make:job, make:mail, make:test, make:seeder, make:factory. Each one generates a correctly namespaced, correctly structured skeleton file in the right directory, following the exact conventions Laravel (and this course) expects, saving you from manually typing out namespace declarations and boilerplate class structure every single time.
A genuinely useful habit: check a command's own options before guessing, rather than reaching for Google first:
php artisan make:model --help
For instance, make:model accepts flags like -m (also create a migration), -f (also create a factory), and -c (also create a controller) in one shot:
php artisan make:model Post -mfc
That single command generates a model, a migration, a factory, and a controller, all correctly wired together, in one step, which is considerably faster than four separate commands and reduces the chance of a typo mismatching a filename somewhere.
Operational Artisan Commands
Beyond generation, Artisan runs real operational commands you will use constantly during both development and deployment. php artisan tinker (mentioned in this project's own CLAUDE.md, though note this specific project disables it, relying on SQL or dedicated Artisan commands instead) opens an interactive PHP shell with your entire application already bootstrapped, letting you call Post::count() or test a relationship directly, without writing a throwaway script file. php artisan config:clear and php artisan cache:clear flush Laravel's internal caches, essential when a configuration change doesn't seem to be taking effect and you suspect a stale cached config file is the culprit. php artisan route:list, covered back in lesson 7, shows every registered route.
Writing A Custom Command
Writing your own Artisan command is genuinely useful the moment you have a repeatable administrative task, generating a report, cleaning up old records, running a scheduled calculation, that doesn't belong inside a web request at all:
php artisan make:command PurgeOldDrafts
namespace App\Console\Commands;
use App\Models\Post;
use Illuminate\Console\Command;
class PurgeOldDrafts extends Command
{
protected $signature = 'posts:purge-drafts {--days=30}';
protected $description = 'Delete unpublished draft posts older than N days.';
public function handle(): int
{
$days = (int) $this->option('days');
$count = Post::where('is_published', false)
->where('created_at', '<=', now()->subDays($days))
->count();
if (! $this->confirm("Delete {$count} draft posts older than {$days} days?")) {
$this->info('Cancelled.');
return self::SUCCESS;
}
Post::where('is_published', false)
->where('created_at', '<=', now()->subDays($days))
->delete();
$this->info("Deleted {$count} old draft posts.");
return self::SUCCESS;
}
}
php artisan posts:purge-drafts --days=60
The $signature string is worth understanding closely: posts:purge-drafts is the command name you type, and {--days=30} defines an optional flag named days with a default value of 30 if the caller doesn't pass one. $this->confirm() pauses and waits for a yes/no answer from whoever is running the command interactively, a genuinely good habit for any destructive command, protecting a human running it manually from an accidental mistake; note that this confirmation step would need to be skipped (or defaulted to "yes") if the same command were ever run non-interactively, say from a scheduled task, since nothing would be there to answer the prompt.
Custom commands become genuinely powerful once combined with Laravel's task scheduler, which lets you define recurring jobs (daily reports, nightly cleanups, hourly syncs) entirely in PHP code, version-controlled alongside the rest of your application, instead of juggling a separate server crontab file that nobody remembers to keep in sync with the codebase:
use Illuminate\Support\Facades\Schedule;
Schedule::command('posts:purge-drafts --days=90')->daily();
Scheduling Commands
With this in place, a single actual crontab entry on the server, running php artisan schedule:run every minute, is the only system-level cron job ever needed; Laravel itself decides, from this PHP definition, exactly when posts:purge-drafts and any other scheduled command actually needs to run.
A practical tip: give every custom command you write a clear $description, visible in php artisan list, and favor options with sensible defaults (like --days=30 above) over requiring every caller to remember and type every argument manually; a command that works correctly with zero arguments, falling back to reasonable defaults, gets used far more safely than one that demands five flags to be remembered correctly every single time.
Progress Bars For Long Jobs
Artisan commands can also report progress for long-running work, using $this->withProgressBar(), which is a small but genuinely useful touch for any command that processes a large number of records, since a frozen-looking terminal with no output is indistinguishable from a crashed process to whoever is watching it run:
$posts = Post::all();
$this->withProgressBar($posts, function (Post $post) {
// process each post
});
Your turn: build the posts:purge-drafts command above against the Post model from earlier lessons, run it with --days=0 against a test database seeded with a few draft posts, and confirm the confirmation prompt correctly shows the count before anything is actually deleted.
KEEP READING