Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Scheduled Action Laravel Package

devsrv/laravel-scheduled-action

View on GitHub
Deep Wiki
Context7

Getting Started

Before you install: check compatibility. The last tagged release is from March 2023 and the package has a single maintainer with zero dependents. Open composer.json in the repo and confirm the illuminate/* constraint covers your Laravel version (a 2023 release typically targets Laravel 9/10, so Laravel 11/12 projects should expect to test or fork). Skim src/ — it is a small package, and reading the trait plus the console command end-to-end takes about ten minutes and will save you hours of guessing.

Minimal setup:

  1. composer require devsrv/laravel-scheduled-action
  2. Publish and run the migration (the package ships a polymorphic table storing the schedulable model, the action to run, and a scheduled_at timestamp): php artisan vendor:publish --provider="Devsrv\ScheduledAction\ScheduledActionServiceProvider" then php artisan migrate.
  3. Add the package's trait to any model that should be able to schedule work:
use Devsrv\ScheduledAction\Traits\HasScheduledActions; // confirm exact namespace in the README

class Post extends Model
{
    use HasScheduledActions;
}
  1. Register the runner in app/Console/Kernel.php (or routes/console.php on newer Laravel) so due actions actually fire:
$schedule->command('scheduled-action:run') // confirm command signature
    ->everyMinute()
    ->withoutOverlapping()
    ->onOneServer();
  1. Verify with php artisan schedule:list and a manual php artisan scheduled-action:run.

Where to look first: the README's usage example, then src/Console/ for the runner command, then the migration to see exactly what columns you can query and index. If there's a tests/ directory, read the feature test — it is usually the most accurate documentation for a package this size.

First use case: the canonical one is lifecycle transition — publish this draft at 09:00 Monday, expire this coupon at midnight, archive this listing after 30 days. Anything where the intent is "change this model's state later" and you don't want a bespoke scheduled_at column plus a one-off console command per model.

Implementation Patterns

Schedule at the moment of intent. Don't backfill from a cron scan; create the scheduled action in the same request that creates or updates the model. This keeps the intent explicit and queryable.

DB::transaction(function () use ($post) {
    $post->update(['status' => 'scheduled']);
    $post->scheduleAction('publish', now()->addDays(3)); // illustrative signature
});

Wrapping both writes in a transaction prevents the classic failure mode where the model says "scheduled" but no action row exists (or vice versa).

Treat the runner as a dispatcher, not a worker. Whatever the package calls to execute the action, keep that body cheap: fire a domain event or dispatch a queued job. If the action body does real work (sending mail, hitting an API, resizing images), the minute-by-minute command will block, and one slow action delays every other due action in the same batch.

// inside the action handler
PublishPost::dispatch($post);

This also gives you retries and failure isolation the runner almost certainly doesn't provide.

Reschedule and cancel explicitly. Model edits should invalidate stale schedules. If a user moves the publish date from Monday to Friday, cancel the pending action and create a new one rather than leaving two rows — otherwise you'll get a Monday publish you thought you'd cancelled. Add a guard (a unique index on (schedulable_type, schedulable_id, action) where status is pending, or an application-level check) so double-submits don't create duplicates.

Query pending work for UI. The table is a normal Eloquent table — expose "upcoming actions" in your admin panel by querying it directly with where('scheduled_at', '>', now()). This is the single biggest day-to-day win: users can see and trust the schedule instead of asking you to check the database.

Good fits: trial expiry, auto-renewal warnings, post publish, auction close, soft-delete/archive after N days, re-engagement emails, reminder notifications. Poor fits: anything needing sub-minute precision, anything needing immediate cancellation guarantees (the runner only sees the row once a minute), or workflows with branching/step dependencies — that's a state machine, not a scheduled action.

Testing pattern:

$this->travelTo(now()->addDays(4));
$this->artisan('scheduled-action:run')->assertExitCode(0);
$this->assertSame('published', $post->fresh()->status);

Freeze time before scheduling, run the command, then assert on the model and on the action row's status. Add one test for "not yet due" to catch off-by-one comparisons.

Gotchas and Tips

Overlapping runs are your problem to solve. Two cron ticks (or two servers) can pick up the same due row. Use withoutOverlapping() and onOneServer() in the schedule definition, and check whether the package marks rows as claimed/in-progress before executing. If it doesn't, wrap the handler in Cache::lock() keyed on the action ID.

Timezone handling is the most common source of "it fired at the wrong time." Confirm whether scheduled_at is cast to a datetime and stored in UTC. Store UTC, compare in UTC, render in the user's timezone. Mixed config('app.timezone') and database session timezones are the usual culprit.

Index the lookup columns. The runner queries by scheduled_at (often with a status filter) every minute, forever. Without an index on scheduled_at this becomes a table scan in production at a size you won't notice in testing.

Polymorphic orphans. Deleting a model leaves its scheduled actions behind; the runner then tries to act on a null. Cascade-delete on model deletion (deleting hook) or filter out rows whose schedulable is missing. Same for soft-deleted models — decide deliberately whether a soft-deleted post should still publish.

Idempotency after a crash. If the process dies mid-run, rows may be executed twice or marked done without executing. Make handlers idempotent (if ($post->status === 'published') return;) and prefer state checks over trusting the package's bookkeeping.

Batch sizing. If the runner does ->get() on all due actions, a backlog (say, after a cron outage) loads everything into memory. Check for chunking; if absent, keep backlogs small or patch it.

Maintenance risk is real. 18 stars, no dependents, last release 2023, MIT. That's fine for an internal tool but treat it as vendored-adjacent: pin the exact version, keep the migration in your own repo, and be prepared to fork. The package is small enough that the realistic alternative — a scheduled_actions table, a trait with scheduleAction()/cancelAction(), and a 40-line console command — is a weekend afternoon. If you need Laravel 12 support or locking guarantees the package lacks, writing it yourself may be cheaper than maintaining a fork.

Extension points worth checking: whether the action can be a class name (so you can inject dependencies) versus a method name on the model (simple but untestable); whether the package fires events before/after execution (use these for logging and metrics); and whether the runner's command accepts filters like --model= or --dry-run for safe debugging in production.

Quick alternative to evaluate: for one or two models, Laravel's native queued jobs with ->delay($when) plus a publish_at column usually beats adding a package — you get retries, Horizon visibility, and no dependency. Reach for this package when you have many models and actions and want a single queryable ledger of pending work.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
roundly-consulting/kubernetes-api-for-laravel
roundly-consulting/plausible-for-laravel
gvp/gvp-laravel-template
metasyncsite/laravel-metasync-client
roundly-consulting/onboarding-for-laravel
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo