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 Trackable Jobs Laravel Package

mateusjunges/laravel-trackable-jobs

Track Laravel queued jobs in the database: see when a job is queued, running, finished, or failed, monitor progress and output, and query job status from your app. A simple trait-based package for adding job tracking to any queueable job.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and publish its assets, then migrate the tracking table:

composer require mateusjunges/laravel-trackable-jobs
php artisan vendor:publish --provider="MateusJunges\TrackableJobs\TrackableJobsServiceProvider"
php artisan migrate

If the provider command publishes more than you need, run php artisan vendor:publish interactively and publish only the package’s config/migration tags. First place to look after install:

  1. config/trackable-jobs.php if published — check the tracked job model, table, and connection options.
  2. The generated migration — confirm the tracked_jobs schema and indexes.
  3. MateusJunges\TrackableJobs\Models\TrackedJob — this is the model you will query for dashboards and APIs.
  4. MateusJunges\TrackableJobs\Traits\Trackable — this is the trait you add to jobs.

Add the trait to a queued job:

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use MateusJunges\TrackableJobs\Traits\Trackable;

class GenerateReport implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Trackable;

    public function __construct(public int $reportId)
    {
    }

    public function handle(): void
    {
        // Generate the report...
    }
}

Your first practical use case should be a user-triggered long-running job, such as a report export or CSV import. Dispatch it normally, capture the tracking identifier, and expose a small status endpoint:

$job = new GenerateReport($report->id);
dispatch($job);

return response()->json([
    'tracking_id' => $job->trackedJob->uuid,
]);

Then poll the tracking record:

use MateusJunges\TrackableJobs\Models\TrackedJob;

$tracked = TrackedJob::where('uuid', $request->tracking_id)->firstOrFail();

return response()->json([
    'status' => $tracked->status,
    'started_at' => $tracked->started_at,
    'finished_at' => $tracked->finished_at,
    'output' => $tracked->output,
]);

The package is not a replacement for Horizon or Sentry. Treat it as a lightweight job-run ledger: it tells you what happened to a specific job, not why the queue is slow or why an exception occurred.

Implementation Patterns

Status polling API for user-facing jobs. Store the tracking UUID against your domain record, e.g. reports.tracking_uuid, rather than making the UI remember it. That gives you authorization checks and lets users revisit the status page later.

$report->update([
    'tracking_uuid' => $job->trackedJob->uuid,
]);

Admin job dashboard. Query the tracking model directly for operations screens:

$jobs = TrackedJob::query()
    ->when(request('status'), fn ($q, $status) => $q->where('status', $status))
    ->when(request('job'), fn ($q, $job) => $q->where('name', 'like', "%{$job}%"))
    ->latest()
    ->paginate(50);

Useful filters: status, job class/name, started_at, finished_at, and failed jobs older than N minutes. Add a “stuck” view for records that have been running longer than expected.

Correlate jobs with domain entities. The package tracks the job, not your business object. Keep the relationship in your own table:

// reports table
tracking_uuid  nullable uuid
status         string
completed_at   nullable timestamp

Then a model observer or job completion check can reconcile the tracked job with the report. This avoids coupling your domain schema to a third-party package’s table.

Write progress/output deliberately. For jobs that produce a result path, summary, or error detail, update the tracked record from the job or from a service it calls. If your package version exposes a helper, use it; otherwise update the TrackedJob row by UUID. Keep output short and non-sensitive. Do not store entire exception traces or personally identifiable payloads in output.

Failure alerting. A scheduled command is usually enough:

TrackedJob::where('status', 'failed')
    ->where('finished_at', '>=', now()->subHour())
    ->each(fn ($job) => Notification::route('slack', config('services.slack.jobs'))
        ->notify(new JobFailed($job)));

If you need “alert once” behavior, add your own notified_at column or cache key. Do not rely on the package to de-duplicate notifications.

Retry-aware monitoring. For jobs with tries and backoff, treat running as a transient state. Alert on a combination of status and elapsed time rather than status alone. A job that has been running for 45 minutes may be retrying, stuck, or killed mid-run.

Testing. Use RefreshDatabase and explicitly test the lifecycle you care about. Queue::fake() often prevents the real handle() path, so pair dispatch assertions with a separate test that runs the job synchronously or invokes handle() directly. Assert the tracking row exists, has the expected status, and is linked to your domain record.

Pruning. Tracking tables grow quickly. Schedule cleanup:

TrackedJob::where('finished_at', '<', now()->subDays(30))->delete();

Keep failed jobs longer than successful ones if you use the table for incident review.

Gotchas and Tips

  • The trait is opt-in. Jobs without Trackable will not appear. If a dashboard looks empty, confirm the job class actually uses the trait and that the queue worker was restarted after deploy.
  • Restart workers after trait changes. Laravel queue workers cache job classes. Run php artisan queue:restart after deploying tracking changes, otherwise old workers may keep running untracked jobs.
  • Tracking starts at dispatch, not necessarily at execution. A job can sit in queued for a long time on a busy queue. Use started_at for execution latency and created_at/queue time for backlog analysis.
  • Synchronous queue drivers collapse the lifecycle. With sync, a job can move from queued to finished almost instantly, so a “running” UI state may never be visible.
  • Failed retries can look confusing. A job that fails an attempt but will retry may not be in a final failed state yet. Check your retry configuration and alert on elapsed runtime for in-progress records.
  • Killed workers can leave stale running records. A fatal PHP error, OOM kill, or worker restart can prevent the package from writing the final status. Build a stale-job detector that flags records running longer than a threshold.
  • Do not edit vendor migrations in place. Publish the migration, modify it before production, and keep a migration strategy for existing environments. If the tracking table already exists, re-publishing can create conflicts.
  • High-volume queues pay a database write per job. For thousands of tiny jobs per minute, tracking every job may add meaningful write load. Limit tracking to imports, exports, reports, webhooks, and other jobs users or operators care about.
  • Treat the tracking UUID as an external identifier. It is usually safe to expose, but do not let it bypass authorization. Always join the tracking UUID back to the user- or tenant-owned domain record.
  • Output is not a log sink. Truncate output, avoid secrets, and remember the column type and length. If you need structured progress, store small JSON payloads or update a domain-specific progress column instead.
  • Multi-tenancy needs your own boundary. The package does not know about tenants. Add a tenant_id to your domain tracking table or extend the package model/table carefully, then scope every dashboard query.
  • Config cache surprises. After changing package config, run php artisan config:clear locally and ensure production deploys rebuild caches.
  • Pin the version and review upgrades. This is a small, focused package. Keep your composer constraint conservative and review migrations/model changes before upgrading, especially if you have extended the tracked job model or table.
  • Use extension tables over vendor table edits. If you need notifications, tenant IDs, business references, or audit metadata, create a job_runs table keyed by the tracked job UUID. That is cleaner than adding many columns to the package’s table and keeps future upgrades simpler.
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.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log