Laravel Queues Guide

Asynchronous job processing in Laravel: job classes, dispatching patterns, failed job handling, batching, and queue worker configuration.

1. Creating a Job

// php artisan make:job ProcessArticleImages

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ProcessArticleImages implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 3;                    // max attempts
    public int $timeout = 120;               // seconds before timeout
    public int $backoff = 60;                // seconds between retries
    public bool $deleteWhenMissingModels = true; // auto-delete if model deleted

    public function __construct(
        private readonly Article $article,
        private readonly array $options = []
    ) {}

    public function handle(ImageProcessor $processor): void
    {
        foreach ($this->article->getRawImages() as $image) {
            $processor->resize($image, $this->options['maxWidth'] ?? 1200);
            $processor->generateWebp($image);
        }
        $this->article->update(['images_processed' => true]);
    }

    public function failed(\Throwable $e): void
    {
        // Called when all retries are exhausted
        \Log::error("Image processing failed for article {$this->article->id}", [
            'error' => $e->getMessage(),
        ]);
        $this->article->update(['images_processed' => false]);
    }
}

2. Dispatching Jobs

use App\Jobs\ProcessArticleImages;

// Dispatch immediately (to configured queue)
ProcessArticleImages::dispatch($article);

// Dispatch with delay
ProcessArticleImages::dispatch($article)->delay(now()->addMinutes(10));

// Dispatch on specific queue
ProcessArticleImages::dispatch($article)->onQueue('media');

// Dispatch on specific connection
ProcessArticleImages::dispatch($article)->onConnection('redis');

// Dispatch if condition
ProcessArticleImages::dispatchIf($article->has_images, $article);
ProcessArticleImages::dispatchUnless($article->images_processed, $article);

// After database commits (avoids race conditions)
ProcessArticleImages::dispatch($article)->afterCommit();

// Sync dispatch (runs immediately, no queue)
ProcessArticleImages::dispatchSync($article);

// Chaining jobs
ProcessArticleImages::dispatch($article)
    ->chain([
        new GenerateThumbnails($article),
        new NotifySubscribers($article),
    ]);

3. Job Batching

use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;

$jobs = $articles->map(fn($a) => new ProcessArticleImages($a))->all();

$batch = Bus::batch($jobs)
    ->name('Process Article Images')
    ->then(function (Batch $batch) {
        // All jobs succeeded
        \Log::info("Batch {$batch->id} completed successfully");
    })
    ->catch(function (Batch $batch, \Throwable $e) {
        // First job failure
        \Log::error("Batch failed: " . $e->getMessage());
    })
    ->finally(function (Batch $batch) {
        // Always runs (success or failure)
    })
    ->allowFailures()   // don't cancel batch on single failure
    ->onQueue('media')
    ->dispatch();

// Check batch progress
$batch = Bus::findBatch($batch->id);
echo $batch->progress(); // 0-100

4. Failed Job Handling

// config/queue.php
'failed' => [
    'driver'   => 'database-uuids',
    'database' => env('DB_CONNECTION', 'mysql'),
    'table'    => 'failed_jobs',
],

// Artisan commands
// php artisan queue:failed        โ€” list failed jobs
// php artisan queue:retry all     โ€” retry all failed jobs
// php artisan queue:retry {id}    โ€” retry specific job
// php artisan queue:forget {id}   โ€” delete specific job
// php artisan queue:flush         โ€” delete all failed jobs

// Custom failed job handler
class SendWelcomeEmail implements ShouldQueue
{
    use InteractsWithQueue;

    public function failed(\Throwable $e): void
    {
        // Can re-dispatch with different config
        $this->release(60); // re-queue with 60s delay

        // Or notify team
        Notification::send(
            User::admin()->get(),
            new JobFailedNotification($this, $e)
        );
    }
}

5. Queue Worker Commands

# Start worker processing 'default' queue
php artisan queue:work

# Process specific queue with options
php artisan queue:work redis --queue=media,default --sleep=3 --tries=3 --timeout=90

# Process single job then exit
php artisan queue:work --once

# Listen (restarts on code changes โ€” dev only)
php artisan queue:listen

# Supervisor config (production)
# /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/www/html/storage/logs/worker.log

6. Queue Drivers Comparison

DriverBest ForPros
syncTesting / simpleNo setup needed
databaseSmall appsBuilt-in, no extra infra
redisProductionFast, supports Horizon
beanstalkdMedium appsSimple, reliable
sqsAWS environmentsFully managed