Laravel Jobs provide a powerful way to handle background processing, asynchronous tasks, and deferred execution. Instead of performing everything during a web request, you can offload long-running, expensive, or repetitive operations into jobs. Jobs are simple PHP classes that contain logic you want to run in the background, such as sending emails, processing video uploads, generating reports, clearing caches, or syncing data. Laravel’s job system is tightly integrated with its queue subsystem, giving developers an elegant API to dispatch, delay, retry, fail, and monitor background tasks.
In modern applications, performance and scalability are crucial. Running heavy tasks synchronously slows down response times and burdens servers. Jobs allow applications to remain fast and responsive while still performing complex operations behind the scenes. This article explores everything you need to know about Jobs in Laravel, including job creation, dispatching, queue workers, middleware, chaining, batching, retry logic, failed jobs, monitoring, queues, drivers, database interactions, events, and real-world examples.
What Are Jobs in Laravel
A Job in Laravel is a simple class that encapsulates a unit of work. When a job is dispatched, it is pushed into a queue to be processed later by a worker. This architecture allows you to run tasks asynchronously.
Jobs are ideal for:
- Sending emails
- Sending notifications
- Processing uploaded files
- Generating PDFs
- Syncing with third-party APIs
- Running scheduled tasks
- Performing database cleanups
- Performing computationally heavy tasks
Every job implements the handle() method which contains the business logic to execute when the worker processes the job.
How Jobs Work With Queues
Jobs are designed to work alongside Laravel’s queue system. The queue allows the framework to delay execution and process tasks in the background.
Workflow:
- You dispatch a job
- Laravel stores it in a queue
- A queue worker picks up the job
- Worker executes the
handle()method - Job completes or fails
The separation of responsibility ensures smoother performance and better scalability.
Creating a Job Using Artisan
To create a job class, run:
php artisan make:job SendEmailJob
This generates a file inside:
app/Jobs/SendEmailJob.php
Laravel automatically scaffolds the structure of the job.
Structure of a Job Class
A typical job class looks like:
class SendEmailJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function handle()
{
Mail::to($this->user->email)->send(new WelcomeMail());
}
}
Important components include:
ShouldQueueinterface: tells Laravel to process asynchronouslyDispatchabletrait: provides dispatch functionalityInteractsWithQueuetrait: access queue-related methodsQueueabletrait: configure queue settingsSerializesModelstrait: serialize Eloquent models safely
Dispatching Jobs
Dispatching a job means pushing it onto the queue.
SendEmailJob::dispatch($user);
You can also use:
dispatch(new SendEmailJob($user));
Passing Data to Jobs
Jobs often require data. You pass them through constructors.
public function __construct(User $user, string $message)
{
$this->user = $user;
$this->message = $message;
}
Dispatch:
SendEmailJob::dispatch($user, 'Welcome to the platform');
Immediate vs Queued Jobs
If a job does not implement ShouldQueue, it runs immediately.
class SimpleJob
{
public function handle()
{
// executes instantly
}
}
For background processing, implement:
implements ShouldQueue
Configuring Queues
Laravel supports multiple queue drivers:
- database
- redis
- beanstalkd
- sqs
- sync (runs immediately)
- null (discards jobs)
Configure in:
config/queue.php
Running Queue Workers
To process jobs:
php artisan queue:work
This starts a worker that listens for jobs.
The queue:listen Command
Alternative:
php artisan queue:listen
Listen is slower because it restarts on each job. queue:work is recommended for production.
Supervising Queue Workers
In production, use Supervisor to keep workers alive.
Example Supervisor configuration:
[program:laravel_queue]
process_name=%(program_name)s_%(process_num)02d
command=php artisan queue:work --sleep=3 --tries=3
Job Middleware
Laravel allows attaching middleware to jobs.
Example:
public function middleware()
{
return [
new WithoutOverlapping($this->user->id),
];
}
Job middleware is similar to HTTP middleware.
Rate Limiting Jobs Using Middleware
public function middleware()
{
return [
new RateLimited('emails'),
];
}
Rate limiting prevents overload.
Delaying Jobs
You can delay job execution.
SendEmailJob::dispatch($user)->delay(now()->addMinutes(5));
Useful for follow-up notifications.
Specifying Queue Names
Jobs can be assigned to specific queues.
SendEmailJob::dispatch($user)->onQueue('emails');
Queue workers can listen to certain queues only.
Prioritizing Queues
You can process high-priority queues first:
php artisan queue:work --queue=high,default
Using the Queueable Trait for Customization
The Queueable trait gives extra configuration:
$this->queue = 'emails';
$this->delay = now()->addMinutes(2);
$this->tries = 5;
Retrying Failed Jobs
Laravel retries jobs automatically.
Configure retries:
php artisan queue:work --tries=5
Handling Job Failures
Failed jobs are stored in the failed_jobs table.
Create the table:
php artisan queue:failed-table
php artisan migrate
Inspecting Failed Jobs
List failures:
php artisan queue:failed
Retry a failed job:
php artisan queue:retry {id}
Retry all:
php artisan queue:retry all
Delete failed job:
php artisan queue:forget {id}
Handling Failure Inside a Job
You can define failed():
public function failed(Exception $exception)
{
Log::error($exception->getMessage());
}
Job Timeouts
Configure timeouts:
public $timeout = 120;
Or in queue worker:
php artisan queue:work --timeout=60
Job Batching
Batching allows grouping multiple jobs.
Create a batch:
Bus::batch([
new SendEmailJob($user1),
new SendEmailJob($user2),
])->dispatch();
You can track:
- progress
- completion
- failure
Handling Batch Completion
->then(function (Batch $batch) {
// batch finished
})
Handling Batch Errors
->catch(function (Batch $batch, Throwable $e) {
// error handling
})
Job Chains
Chaining ensures jobs run in sequence:
SendEmailJob::withChain([
new LogEmailJob(),
new UpdateUserStatusJob(),
])->dispatch($user);
Chain ensures tasks execute in order.
Synchronous Dispatching for Testing
SendEmailJob::dispatchSync($user);
Runs job immediately and synchronously.
Dispatching Jobs After Database Commit
Sometimes you want to wait for a transaction.
SendEmailJob::dispatchAfterResponse($user);
Or:
SendEmailJob::dispatch($user)->afterCommit();
Ensures job runs only when transaction succeeds.
Serialization of Models in Jobs
Jobs use SerializesModels trait.
It serializes model IDs only, avoiding entire model serialization.
Example:
public function __construct(User $user)
{
$this->user = $user;
}
The model is rehydrated when job runs.
Avoiding Serialization Issues
Closures cannot be serialized.
Never pass:
dispatch(function () {
//
});
Unless using closure-based queueing.
Queued Event Listeners vs Jobs
Jobs are general-purpose tasks. Event listeners are event-driven.
Listeners implementing ShouldQueue are jobs triggered by events.
class SendEmailListener implements ShouldQueue
Jobs vs Notifications
Notifications can be queued automatically if the notification class uses ShouldQueue.
Jobs are more flexible and low-level.
Jobs vs Commands
Commands are for console operations. Jobs run in the queue worker.
They serve different use cases.
Using Queued Mail
Mailables can be queued without creating a job:
Mail::to($user)->queue(new WelcomeMail());
Using Queued Notifications
Similar:
$user->notify(new WelcomeNotification());
Queued Model Events
You can queue event listeners for:
- created
- updated
- deleted
Example:
protected $dispatchesEvents = [
'created' => SendEmailJob::class,
];
Using Dispatch Helper Globally
dispatch(new SendEmailJob($user));
Scheduling Jobs
Using Task Scheduler:
$schedule->job(new CleanupJob())->daily();
Queuing Jobs on Specific Connection
SendEmailJob::dispatch($user)->onConnection('redis');
Database Queue Driver
Database driver stores jobs in the jobs table.
Create table:
php artisan queue:table
php artisan migrate
Redis Queue Driver
Redis is ideal for large-scale apps.
Configure in .env:
QUEUE_CONNECTION=redis
Laravel Horizon for Queue Monitoring
Horizon provides:
- real-time metrics
- failed job logs
- throughput tracking
- dashboard
Install Horizon for Redis-based queues:
composer require laravel/horizon
Returning Values From Jobs
Jobs should not return values because workers run asynchronously.
Instead, store results in:
- DB
- caches
- logs
Avoiding Duplicate Job Execution
Use middleware:
new WithoutOverlapping($user->id)
Avoiding Job Overload
Use rate limiting middleware:
new RateLimited('emails')
Writing Idempotent Jobs
Idempotent jobs avoid duplication issues.
For example:
- Check if email was already sent
- Use unique constraints
- Locking
Logging Inside Jobs
Log::info('Job started', ['user' => $this->user->id]);
Testing Queued Jobs
Fake the queue:
Queue::fake();
Dispatch:
SendEmailJob::dispatch($user);
Assert:
Queue::assertPushed(SendEmailJob::class);
Example Use Case: Sending Welcome Emails
class SendWelcomeEmail implements ShouldQueue
{
public function handle()
{
Mail::to($this->user->email)
->send(new WelcomeMail($this->user));
}
}
Dispatch:
SendWelcomeEmail::dispatch($user);
Example Use Case: Upload Processing
class ProcessUpload implements ShouldQueue
{
public function handle()
{
// image compression
// video encoding
// large file processing
}
}
Example Use Case: Third-Party API Sync
class SyncWithCRM implements ShouldQueue
{
public function handle()
{
Http::post('https://crm.com/api', $this->data);
}
}
Best Practices for Jobs in Laravel
- Keep jobs small and focused
- Avoid heavy logic inside controllers; use jobs instead
- Use queues for long-running tasks
- Use redis for high-performance queues
- Use Horizon for monitoring large queue systems
- Make jobs idempotent to avoid duplication
- Avoid passing complex objects; use IDs
- Use middleware for rate limiting and overlapping prevention
- Store results in database or cache
- Always handle failures
- Use batches for grouped processing
- Use chains for dependent tasks
- Use dispatchAfterResponse when necessary
Example of a Fully Configured Job
class SendEmailJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 3;
public $timeout = 60;
public function __construct(public User $user)
{}
public function handle()
{
Mail::to($this->user->email)->send(new WelcomeMail());
}
public function failed(Throwable $e)
{
Log::error('Send email failed', [
'user_id' => $this->user->id,
'error' => $e->getMessage()
]);
}
}
Leave a Reply