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 Job Status Laravel Package

twigger/laravel-job-status

Track Laravel queue jobs with persistent status records, progress updates, and error details. twigger/laravel-job-status helps you monitor what’s queued, running, completed, or failed, making background job visibility and debugging easier.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install the package

    composer require twigger/laravel-job-status
    

    The package ships a service provider that should be auto-discovered. If not, register Twigger\JobStatus\JobStatusServiceProvider in config/app.php.

  2. Publish and run the migrations

    php artisan vendor:publish --provider="Twigger\JobStatus\JobStatusServiceProvider"
    php artisan migrate
    

    This creates the job-status table(s). Look at the published migration first — it tells you exactly which columns (status, queue, attempt, timestamps, exception, etc.) are available for querying.

  3. Read the config file

    config/job-status.php is the source of truth. It controls which jobs are tracked, which database connection the status table uses, and any pruning/retention settings. Open this before touching any application code.

  4. Track your first job

    Add the package's tracking trait to a queued job (or, if the package supports it, list the class under the config's tracked-jobs array):

    use Twigger\JobStatus\Concerns\TracksJobStatus;
    
    class GenerateReport implements ShouldQueue
    {
        use TracksJobStatus;
    
        public function handle(): void
        {
            // ...
        }
    }
    
  5. Dispatch and inspect

    GenerateReport::dispatch($user);
    
    // Later, e.g. in a controller or Livewire component:
    $status = JobStatus::where('job', GenerateReport::class)
        ->latest()
        ->first();
    
    $status->status; // queued / started / succeeded / failed
    

First real use case: a "download my export" flow. Dispatch the job, persist the returned JobStatus ID on your export record, then use a small polling endpoint or Livewire component to read the latest status and show "Queued → Processing → Ready" in the UI.

Implementation Patterns

Treat the status row as a join table between your domain and the queue. The most maintainable pattern is to create a domain record first (an Export, Invoice, Import), dispatch the job with that record's ID, and store the resulting JobStatus ID on it. Your UI then queries your own model, not the package's, and you can later swap or remove the package without rewriting business logic.

$export = Export::create(['user_id' => auth()->id(), 'state' => 'queued']);
GenerateExportJob::dispatch($export);

In the job's handle(), update your domain model to processing at the start and ready at the end — do not rely solely on the package for domain state, since the package tracks the queue lifecycle, not your business lifecycle.

Query patterns worth keeping in a repository/scope class:

// Still-running jobs
JobStatus::whereIn('status', ['queued', 'started'])->get();

// Recent failures with exception text
JobStatus::where('status', 'failed')
    ->where('created_at', '>=', now()->subDay())
    ->get();

// Latest status for a specific job class
JobStatus::where('job', GenerateReport::class)
    ->latest('id')
    ->first();

Failure notifications. Rather than polling, listen for the package's failure event (or, if it doesn't expose one, listen to Laravel's Illuminate\Queue\Events\JobFailed and join on the status row). Send a notification to the user who owns the domain record — the status record itself doesn't know your user, so carry the owner ID through the job payload.

Integrate with Laravel's queue events, not with Horizon's UI. Horizon is for operators; this package is for surfacing job state to end users. Keep Horizon installed for infrastructure and use job-status for product features.

Testing. With Queue::fake() no queue events fire, so no status rows are created — that's fine for testing dispatch, but if your assertions involve the status model you need to either use the sync driver or manually create status rows with the model's factory. Assert against your domain state in feature tests; use status-model assertions only in tests that specifically exercise the tracking layer.

Cleanup. Add a scheduled command (or the package's prune command if provided) to delete status rows older than N days. Otherwise the table grows unbounded on high-throughput queues.

Gotchas and Tips

  • Only tracked jobs are recorded. If a status row never appears, the first thing to check is whether the job has the trait / is listed in config/job-status.php, and whether the worker was restarted after the config change (php artisan queue:restart).

  • Retries multiply rows. A job that fails three times typically produces multiple status records. Any "current status" query must explicitly pick the latest (latest('id') or latest('attempt')), otherwise dashboards will show an old failure next to a new success.

  • Don't cache the status model on the job instance. Queued jobs are serialized; the model will be a stale snapshot by the time the worker runs. Store the primary key and re-query inside handle().

  • Sync driver and dispatchSync behave differently. Events may fire in a different order, so started_at/finished_at can be nearly identical or null in some paths. Don't write timestamp assertions that assume a queued lifecycle.

  • Status writes cost a database round-trip per transition. For high-volume queues this is measurable. Put the status table on the same connection as your business data if you need joins; put it on a separate connection only if the write cost is genuinely a problem and you accept the eventual-consistency trade-off.

  • Long-running jobs look stuck. A job that runs for forty minutes shows started the entire time. If your UI says "processing," that's accurate but not reassuring — add your own progress field on the domain record and update it from inside the job.

  • The exception column is truncated. Full stack traces live in failed_jobs / your logs. Use the status row to detect failures and link through to the real source, not as the debugging record.

  • Package age. Last release mid-2023, low star count, zero dependents — pin the version and run the full test suite after bumping Laravel. Verify it still boots on Laravel 11/12 before adopting it in a new project; if it breaks, the event-listener approach is small enough to reimplement in an afternoon.

  • Extension point: the model. The status model and its table are the stable surface. If you need extra metadata, add columns via your own migration rather than forking the package — the package's queries will ignore them and yours can read them.

  • Don't wrap the package in your controllers directly. Write one small JobStatusPresenter/service that converts a status row into a UI-friendly DTO (state, label, progress, error). That single seam is what makes it trivial to remove or replace the package later.

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin