llaski/nova-scheduled-jobs
A Laravel Nova package that brings your scheduled tasks into the Nova dashboard. View every scheduled job, inspect cron expressions and next run times, run jobs manually, and toggle them on or off without leaving your admin panel.
This package is a Nova tool, not a library — it renders Laravel's Schedule singleton as a table inside your existing Nova admin panel. Treat it as a live inventory of registered jobs, not as monitoring.
Minimal install (two steps, no migrations):
composer require llaski/nova-scheduled-jobs
// app/Nova/NovaServiceProvider.php
use Llaski\NovaScheduledJobs\NovaScheduledJobs;
public function tools()
{
return [
new NovaScheduledJobs(),
];
}
Then open Nova → Scheduled Jobs. There is no config to write, no asset compile step, and no write path to your database. Backing it out is deleting the line and running composer remove.
Where to look first:
composer.json at the installed tag — check the laravel/nova and illuminate/* constraints against your stack. The 6.1.3 line was tagged August 2023; Nova 5 / Laravel 11+ is unverified, so prove it on staging before it lands on a shared admin.app/Nova/NovaServiceProvider.php — confirm the tool is registered exactly once.app/Console/Kernel.php on Laravel ≤10, routes/console.php or bootstrap/app.php on Laravel 11+.First use case: put the Nova page and php artisan schedule:list side by side on your first deploy. They should show the same set of jobs. If they disagree, you have a registration-context problem (the schedule isn't loaded on web requests), not a rendering bug — and that distinction is the single most useful debugging habit with this package. From there, the daily use is answering "is the nightly job still scheduled, and when does it next fire?" without SSH.
Name every closure. The table renders each event's description or command string. An unnamed Schedule::call() is an opaque row that helps nobody:
$schedule->call(fn () => SearchIndex::rebuild())
->dailyAt('02:00')
->name('Rebuild search index')
->description('Nightly full reindex — runs on one server')
->onOneServer()
->withoutOverlapping();
Keep one source of truth. Register the schedule in one place so the tool reads the same singleton your console kernel does. Duplicating Schedule:: calls across providers or a package's service provider produces duplicate rows, which read like a package bug but are a registration bug.
Restrict visibility with canSee(). The tool is visible to every Nova user by default, and cron internals are operational detail most users don't need:
public function tools()
{
return [
(new NovaScheduledJobs())->canSee(
fn ($request) => $request->user()->isAdmin()
),
];
}
Annotate guardrails in descriptions. The package does not surface withoutOverlapping(), onOneServer(), or runInBackground() — a guarded job looks identical to an unguarded one. If the team needs to know a job is single-server or non-overlapping, put it in ->description() where people will actually read it.
Use it as a triage tool, paired with the CLI. The workflow that pays for itself: a report of "the nightly export didn't run" → open the Nova page to confirm the job is still registered and see its next run → php artisan schedule:list to confirm the same from the CLI → Horizon/failed-jobs/logs for whether it actually executed. The package answers registration questions only; success and failure live elsewhere.
Add it to the deploy runbook. One line — "verify the Scheduled Jobs page after deploy" — catches jobs whose registration silently moved between Kernel.php, a service provider, and routes/console.php, or whose cron expression drifted during a refactor. This is cheap and catches real regressions.
Extend with a separate card, not by fighting the tool. If you need last-run times, exit codes, or runtime, don't try to add columns to a read-only Vue table. Add a schedule_runs table written from a Schedule::after() hook (or a queued event listener) and surface it in your own Nova metric or card. Keep the package's job narrow.
Scope it per environment. On staging and production it earns its place; on a local dev dashboard it's clutter. Gate it with canSee() by environment or role rather than maintaining a fork.
Schedule::call(fn () => ...) with no ->name() gives you a row you can't identify. Fix the schedule definition, not the package.routes/console.php / bootstrap/app.php; if the web request never loads them, the Nova page renders empty. Diagnose in one command: if php artisan schedule:list shows the job and the page doesn't, it's a bootstrapping issue. Move definitions to a provider that boots on every request.->timezone('America/New_York') can disagree with what you expect from config('app.timezone'). Before filing a bug, compare against schedule:list and the server's date.app/, routes/console.php, and every package service provider for repeated Schedule:: calls before blaming the package.Schedule::events(), so a replacement Nova tool that renders the same collection is a short job if the package goes stale.canSee() or a Nova policy is the fix, not a package setting.php artisan schedule:list cleanly separates "the job isn't registered" from "the page isn't rendering it". Reaching for the browser devtools before running that command wastes time.How can I help you explore Laravel packages today?