laravel-admin-ext/scheduling
A laravel-admin extension that brings Laravel's task scheduler into the admin panel: browse scheduled commands and cron expressions, enable or disable jobs, run tasks manually, and manage schedules without editing code.
Install via Composer in your laravel-admin project:
composer require laravel-admin-ext/scheduling
Register the extension in the admin panel:
php artisan admin:import scheduling
This creates the menu entry and wires the extension's routes/views into config/admin.php (extensions array). If your config is cached, run php artisan config:clear.
Open the admin panel and look for the Scheduling menu item (typically under System or Helpers depending on your menu config). That page is the whole extension: a read-only table of every event in Illuminate\Console\Scheduling\Schedule, with expression, command, next/last run, and a Run button.
Where to look first in your own code: app/Console/Kernel.php (or the schedule() method in routes/console.php / service providers on newer Laravel). That is the single source of truth the extension reflects over. Add a ->description('...') to a task, reload the page, and you'll see it render.
First use case: you've just written $schedule->command('reports:prune')->dailyAt('02:00'); and don't want to wait until 2 AM or fake the clock. Hit Run in the UI, watch the output, fix it, repeat. This is the package's core value — a manual trigger for scheduled jobs without SSH.
Describe and name every task. The listing is only as useful as the metadata you give it. Always chain ->description(); prefer ->command('reports:prune') or ->job(...) over ->call(function () { ... }), because closures render as opaque strings and are often unrunnable from the UI.
$schedule->command('reports:prune')
->dailyAt('02:00')
->description('Prune stale report exports')
->withoutOverlapping();
Treat manual runs as a smoke-test harness, not production tooling. The Run button executes the event in the current web request. Use it in local/staging to validate arguments and output; for production, keep the actual execution on the cron-driven schedule:run. If you must allow it in prod, wrap long jobs behind a queued command so the web request returns immediately.
Push logic into Artisan commands, not closures. Since the extension can only reason about named commands, any non-trivial ->call(...) should become a proper command class. This also gives you consistent output capture and lets the same command be run via CLI or the UI.
Gate the menu by role. The extension respects laravel-admin's menu/permission system, so the practical pattern is: import the menu, then in Admin → Menu restrict the Scheduling item to a super or ops role. Don't leave it visible to every administrator — it gives arbitrary command execution within the schedule.
Environment-gate the registration. In config/admin.php, conditionally remove the extension or the menu entry outside non-production:
'extensions' => array_filter([
'scheduling' => env('APP_ENV') !== 'production' ? [] : null,
]),
Or hide the menu item via a Menu:: override in a service provider.
Pair with withoutOverlapping() and onOneServer(). These are still enforced for manual runs (the mutex is checked the same way), which is usually what you want — it prevents you double-firing a job someone else already triggered. Be aware that a manual run will be silently skipped if a cron run is mid-flight.
Capture output deliberately. If a command uses ->appendOutputTo(storage_path('logs/task.log')), the UI may show nothing for that run. For tasks you frequently test from the UI, rely on $this->output / info() in the command so the extension can surface it, or check the log file directly.
Use it as an audit aid. The expression + description table is a quick way to answer "what's actually scheduled in this app?" — including package-registered tasks you forgot about. Sort by next run to spot collisions (three jobs at 02:00 is a smell).
->call(fn () => ...) tasks appear in the list but often can't be executed from the UI (or execute with no output). If the Run button 500s, this is the first thing to check.schedule:run cron fires on the server's clock. If APP_TIMEZONE and the OS timezone differ, the UI will lie to you. Verify with date on the box.withoutOverlapping leaves the cache mutex in place and every subsequent run is skipped invisibly. Fix with php artisan schedule:clear-cache (Laravel 8+) or by deleting the cache key.max_execution_time, PHP-FPM timeouts, and memory limits. A task that's fine under CLI (php -d memory_limit=-1) can blow up in the browser. Keep manual-testable tasks short, or queue them.* * * * * cd /path && php artisan schedule:run >> /dev/null 2>&1 in the crontab; the UI is purely observational + manual trigger.php artisan admin:import scheduling twice creates a second menu row. Delete the stale one in Admin → Menu rather than re-importing blindly.php artisan route:list | grep schedul).spatie/laravel-schedule-monitor for observability). This extension is a viewer, not a scheduler manager — set expectations accordingly.How can I help you explore Laravel packages today?