t-labs-co/command-scheduler
t-labs-co/command-scheduler is a Laravel package for scheduling and managing Artisan and console commands. Define command schedules in code, control timing and recurrence, and keep routine tasks organized without complex cron setup. Simple APIs help you register, run, and monitor scheduled commands.
v1.0.0 is the first tagged release, which matters more than the (empty) changelog suggests: the public API is now what you're expected to build against, and from here on you can rely on semver for upgrades. The release notes contain no migration steps, deprecations, or feature list — just a link to the commit history — so treat this as a "read the source before you trust it" package rather than a documented one.
Minimal path to first value:
composer require t-labs-co/command-scheduler and confirm the service provider is auto-discovered (php artisan about or php artisan vendor:publish --provider=...). If discovery is off, register it manually in bootstrap/providers.php (Laravel 11+) or config/app.php.src/ top to bottom. It's a v1.0.0 with one commit-linked changelog; the README is likely the only prose you get, so the provider's register()/boot() and the main facade/entry class are your actual documentation.$schedule->command(...) line out of routes/console.php / app/Console/Kernel.php and express it through the package instead. One command, one schedule entry, run it manually with php artisan schedule:run and watch what it does to the log. Only after that works should you port the rest.If the package's value proposition (queueing commands, dynamic/DB-driven schedules, per-run bookkeeping) doesn't match what you see in src/, stop here — v1.0.0 is cheap to back out of, and far more expensive to unwind after you've migrated forty schedule entries.
Centralise the schedule definition. The main win of a package like this is getting scheduling logic out of Kernel.php/routes/console.php and into something testable. Keep a single registry (a config array, a DB table, or a dedicated class) as the source of truth, and let the package's bootstrapping read from it. Anything you'd otherwise hardcode in the kernel should live there.
Wrap existing commands rather than rewriting them. Schedule through the package but keep the command classes plain Laravel commands. That way the package is a thin scheduling layer you can remove, not a framework your commands are welded to. If the package requires extending a base command class, isolate those in one namespace so the blast radius is visible.
Guard against overlap and concurrency explicitly. Whatever the package offers for withoutOverlapping() / onOneServer() semantics, set it deliberately per task. For a package whose selling point is scheduling, the failure mode you'll actually hit in production is a task piling up on itself — and if the package manages its own mutex, it may not respect the framework's. Verify with two workers.
Test the schedule, not the scheduler. Assert on whatever structure the package exposes (the resolved list of due tasks, a task registry, a value object per entry) rather than sleeping and invoking schedule:run. Use Carbon::setTestNow() and travel in time. If nothing is inspectable, that's a design smell worth raising upstream.
Integration with the rest of the stack: point the package's log channel at a dedicated channel so scheduled-run output doesn't pollute application logs; route jobs to a named queue so a runaway schedule can't starve user-facing work; and if it dispatches jobs rather than commands, make sure your queue worker's --timeout is comfortably above your longest scheduled task.
Rollout pattern for a schedule-heavy app: migrate one low-risk task, run it in parallel with the existing entry (different log channel, output compared), then delete the old one. This package has no upgrade history to learn from — you're establishing the baseline.
"t-labs-co/command-scheduler": "^1.0" is fine, but don't float to *) and read the diff on every minor bump.config('app.timezone'), a package-level timezone, or the server's. Set it explicitly and assert it in a test.withoutOverlapping(). Two entries for the same logical task can then run simultaneously. Pick one mechanism and use it everywhere.failed event listener. Test by deliberately throwing inside a scheduled command.php artisan schedule:list reflects Laravel's kernel, not necessarily the package's registry — don't assume they agree. Cross-check with the package's own inspection command if one exists, and fall back to schedule:run -v plus a temporary Log::info() in the package's dispatch path.$schedule->command() behind a flag. If the package stalls (no releases, no response to issues), switching back should be a config change, not a rewrite.How can I help you explore Laravel packages today?