Updated June 2026. Tested on Laravel 13 and PHP 8.4. Scheduling lives in routes/console.php via the Schedule facade since Laravel 11. Part of the Techalyst scheduling series, starting with Task Scheduling in Laravel.

Every entry you add to the scheduler becomes a task object carrying a full set of properties: what to run, when, in which timezone, under what conditions, and what to do around it. Here is the complete toolbox, so you know exactly what you can dial in.

The four things you can schedule

A scheduled task can run any of four kinds of work.

An artisan command, by signature or class name.

use Illuminate\Support\Facades\Schedule;

Schedule::command('mail:send --user=10')->monthly();

A queued job, by class or instance, which Laravel dispatches for you.

Schedule::job(new SendOffer(10))->monthly();

A shell command on the operating system.

Schedule::exec('php /home/sendmail.php --user=10')->monthly();

A closure, run through the container.

Schedule::call(fn () => DB::table('recent_users')->delete())->daily();

Internally these reduce to two kinds of event: a command/exec event, and a callback event (used by call), with the job and command helpers built on top.

Frequency becomes a cron expression

You almost never write raw cron. The frequency methods build the expression for you. A new task defaults to "every minute" and each method rewrites it.

Schedule::command('mail:send')->hourly();      // 0 * * * *
Schedule::command('mail:send')->dailyAt('13:30'); // 30 13 * * *
Schedule::command('mail:send')->twiceDaily(5, 14); // 0 5,14 * * *

If you do want full control, pass your own expression.

Schedule::command('mail:send')->cron('0 * * * *');

Laravel uses a cron-expression library to decide, each minute, whether the task is due based on the current time.

Timezones

By default the expression is evaluated in your app's timezone. Override it per task when a job is tied to a particular region.

Schedule::command('reports:eu')->dailyAt('09:00')->timezone('Europe/London');

Constraints: when a task may run

On top of the schedule, you can layer conditions that decide whether a due task actually runs.

Date ranges. Run only within a window, or skip a window.

Schedule::command('promo:run')->daily()->between('2026-06-01', '2026-06-30');
Schedule::command('promo:run')->daily()->unlessBetween('00:00', '06:00');

Environments. Limit a task to certain environments.

Schedule::command('metrics:sync')->hourly()->environments('staging', 'production');

Maintenance mode. Scheduled tasks are skipped while the app is down for maintenance, unless you opt in.

Schedule::command('health:ping')->everyMinute()->evenInMaintenanceMode();

Custom conditions. Decide with a closure using when (run only if true) or skip (skip if true).

Schedule::command('digest:send')->daily()->when(fn () => User::count() > 1000);
Schedule::command('digest:send')->daily()->skip(fn () => app()->isLocal());

System user. Run the command as a specific OS user, which Laravel does via sudo -u.

Schedule::command('mail:send')->daily()->user('forge');

Lifecycle callbacks

Finally, you can hook into the task running. Run code before and after, or ping a URL, which is how you wire scheduled tasks to a monitor like a dead-man's-switch service.

Schedule::command('backup:run')
    ->daily()
    ->before(fn () => Log::info('backup starting'))
    ->after(fn () => Log::info('backup done'))
    ->onSuccess(fn () => Log::info('backup ok'))
    ->onFailure(fn () => Alert::send('backup failed'))
    ->pingBefore('https://monitor.example.com/start')
    ->thenPing('https://monitor.example.com/finish');

The ping helpers are especially handy: a monitoring service expects a request when a task starts and finishes, and alerts you if it ever does not arrive.

Wrapping up

A scheduled task is far more than a command and a time. You can schedule a command, a queued job, a shell command, or a closure; shape the timing with frequency methods (or raw cron) and a timezone; gate it with date ranges, environments, maintenance mode, and custom when/skip conditions; run it as a chosen user; and wrap it in before, after, success, failure, and ping callbacks. Knowing the whole set means you can express almost any scheduling rule cleanly in code.

That rounds out the scheduling series. Start at Task Scheduling in Laravel, or revisit preventing overlap. Questions welcome below.