Web applications often need to perform tasks that take several seconds to complete: sending emails, resizing images, calling external APIs, or generating reports. Running these tasks synchronously during an HTTP request means users wait until the work finishes.Laravel’s queue system lets you push these tasks to a background queue and return a response immediately. A worker process picks up the jobs and executes them separately.
Laravel supports multiple queue backends—database, Redis, Amazon SQS, and more. During development, the sync driver executes jobs immediately without a real queue, so no worker is required.
Amazon SQS limits the maximum queued message payload size. If your jobs may exceed that limit, you can store oversized payloads in a cache store and send only a pointer through SQS:
<?phpnamespace App\Jobs;use App\Models\User;use App\Mail\WelcomeMail;use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Foundation\Queue\Queueable;use Illuminate\Support\Facades\Mail;class SendWelcomeEmail implements ShouldQueue{ use Queueable; public function __construct( public User $user, ) {} public function handle(): void { Mail::to($this->user->email)->send(new WelcomeMail($this->user)); }}
Implementing ShouldQueue tells Laravel to push the job onto the queue instead of running it synchronously. The Queueable trait provides the methods needed to configure and dispatch the job.
When you pass an Eloquent model to a job constructor, Laravel serializes only the model’s ID. The worker re-fetches fresh data from the database at execution time, keeping the queue payload small.
Chain jobs so they run sequentially. If one job in the chain fails, subsequent jobs are not run:
use App\Jobs\ProcessPodcast;use App\Jobs\OptimizePodcast;use App\Jobs\ReleasePodcast;use Illuminate\Support\Facades\Bus;Bus::chain([ new ProcessPodcast($podcast), new OptimizePodcast($podcast), new ReleasePodcast($podcast),])->dispatch();
You can also attach a callback that runs when the entire chain completes:
Bus::chain([ new ProcessPodcast($podcast), new ReleasePodcast($podcast),])->catch(function (Throwable $e) { // A job in the chain failed...})->dispatch();
Batching lets you dispatch a collection of jobs and track their collective progress. Start by creating a migration for the job_batches table:
php artisan make:batches-tablephp artisan migrate
Implement the Batchable trait in your job class:
<?phpnamespace App\Jobs;use Illuminate\Bus\Batchable;use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Foundation\Queue\Queueable;class ImportContacts implements ShouldQueue{ use Batchable, Queueable; public function handle(): void { if ($this->batch()->cancelled()) { return; } // Import a chunk of contacts... }}
Dispatch a batch using Bus::batch:
use App\Jobs\ImportContacts;use Illuminate\Bus\Batch;use Illuminate\Support\Facades\Bus;use Throwable;$batch = Bus::batch([ new ImportContacts($chunkA), new ImportContacts($chunkB), new ImportContacts($chunkC),])->then(function (Batch $batch) { // All jobs completed successfully})->catch(function (Batch $batch, Throwable $e) { // A job failed})->finally(function (Batch $batch) { // The batch has finished executing})->dispatch();
Inspect a batch by its ID:
$batch = Bus::findBatch($batchId);$batch->totalJobs; // Total job count$batch->pendingJobs; // Jobs still waiting$batch->failedJobs; // Jobs that failed$batch->progress(); // Completion percentage (0–100)
# Process only the 'emails' queue on Redisphp artisan queue:work redis --queue=emails# Use the database connectionphp artisan queue:work database
queue:work runs continuously. After deploying new code, run php artisan queue:restart to reload workers with the latest changes. In production, use a process manager like Supervisor to keep workers running.
# List all failed jobsphp artisan queue:failed# Retry a specific jobphp artisan queue:retry ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece# Retry all failed jobsphp artisan queue:retry all# Delete a specific failed jobphp artisan queue:forget ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece# Delete all failed jobsphp artisan queue:flush
<?phpnamespace App\Jobs;use App\Models\Order;use App\Mail\OrderConfirmed;use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Foundation\Queue\Queueable;use Illuminate\Queue\Attributes\Tries;use Illuminate\Queue\Attributes\Timeout;use Illuminate\Support\Facades\Mail;#[Tries(3)]#[Timeout(30)]class SendOrderConfirmation implements ShouldQueue{ use Queueable; public function __construct( public Order $order, ) {} public function handle(): void { Mail::to($this->order->user->email) ->send(new OrderConfirmed($this->order)); } public function failed(?Throwable $exception): void { // Notify the team about the failure }}
3
Dispatch from the controller
use App\Jobs\SendOrderConfirmation;public function store(Request $request): RedirectResponse{ $order = Order::create($request->validated()); SendOrderConfirmation::dispatch($order); return redirect()->route('orders.show', $order) ->with('success', 'Order placed successfully.');}
Set QUEUE_CONNECTION=sync in your .env during development. Jobs run immediately without a worker, making it easy to test the full flow without extra processes.
QUEUE_CONNECTION=sync
Common commands reference
# Start the workerphp artisan queue:work# Restart workers after deploymentphp artisan queue:restart# List failed jobsphp artisan queue:failed# Retry all failed jobsphp artisan queue:retry all# Delete all failed jobsphp artisan queue:flush