> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Queues

> Run time-consuming tasks in the background with Laravel queues—covering setup, job creation, chaining, batches, and failed job handling.

## What are queues?

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.

<Info>
  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.
</Info>

```mermaid theme={null}
flowchart LR
    A["Job created<br>dispatch()"] --> B["Queue driver<br>(Redis / DB / etc.)"]
    B --> C["Worker<br>queue:work"]
    C --> D{"Success?"}
    D -->|"Yes"| E["Done"]
    D -->|"No"| F{"Retry?"}
    F -->|"Yes"| B
    F -->|"No"| G["Recorded as failed<br>failed_jobs"]
```

## Queue setup and configuration

### config/queue.php

All queue configuration lives in `config/queue.php`. Switch the backend by setting the `QUEUE_CONNECTION` environment variable:

```php theme={null}
// config/queue.php
'default' => env('QUEUE_CONNECTION', 'database'),
```

### .env settings

```ini theme={null}
# Use the database driver
QUEUE_CONNECTION=database

# Or switch to Redis
# QUEUE_CONNECTION=redis
# REDIS_HOST=127.0.0.1
# REDIS_PORT=6379
```

### Setting up the database driver

The `database` driver stores jobs in a database table. In Laravel 11+, new projects already include the required migration. If yours doesn't, run:

```shell theme={null}
php artisan make:queue-table
php artisan migrate
```

### Setting up the Redis driver

Install the Predis package and configure a Redis connection in `config/database.php`:

```shell theme={null}
composer require predis/predis
```

Then set `QUEUE_CONNECTION=redis` in `.env`.

### SQS overflow storage

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:

```php theme={null}
'sqs' => [
    // ...
    'overflow' => [
        'enabled' => env('SQS_OVERFLOW_ENABLED', false),
        'store' => env('SQS_OVERFLOW_STORE'),
        'always' => false,
        'delete_after_processing' => true,
        'flush_on_clear' => env('SQS_OVERFLOW_FLUSH_ON_CLEAR', false),
    ],
],
```

* When `enabled` is true, payloads that are **at least 1 MB** are stored in the configured cache store.
* When `always` is `true`, every SQS payload is stored in the cache store regardless of size.
* `delete_after_processing` removes stored payloads after successful processing (default: `true`).
* If `flush_on_clear` is `true`, `queue:clear` flushes the overflow store. Use a dedicated cache store so normal cache data is not removed.

## Creating and dispatching jobs

### Generating a job class

Create a job with the `make:job` Artisan command:

```shell theme={null}
php artisan make:job SendWelcomeEmail
```

This generates `app/Jobs/SendWelcomeEmail.php`.

### Job class structure

```php theme={null}
<?php

namespace 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.

<Tip>
  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.
</Tip>

### Dispatching a job

Push a job onto the queue by calling `dispatch`:

```php theme={null}
use App\Jobs\SendWelcomeEmail;

public function register(Request $request): RedirectResponse
{
    $user = User::create($request->validated());

    SendWelcomeEmail::dispatch($user);

    return redirect('/dashboard');
}
```

### Dispatching to a specific queue

Send a job to a named queue to separate different priorities:

```php theme={null}
SendWelcomeEmail::dispatch($user)->onQueue('emails');
```

### Queue routing

Instead of calling `onQueue()` and `onConnection()` on every job dispatch, use the `Queue` facade's `route` method in a service provider's `boot()` to define default routing rules for specific job classes:

```php theme={null}
use App\Concerns\RequiresVideo;
use App\Jobs\ProcessPodcast;
use App\Jobs\ProcessVideo;
use Illuminate\Support\Facades\Queue;

public function boot(): void
{
    Queue::route(ProcessPodcast::class, connection: 'redis', queue: 'podcasts');
    Queue::route(RequiresVideo::class, queue: 'video');
}
```

You can route by interface, trait, or parent class — any job that implements, uses, or extends it will inherit the rule.

To route multiple job classes at once, pass an array:

```php theme={null}
Queue::route([
    ProcessPodcast::class => ['podcasts', 'redis'], // queue and connection
    ProcessVideo::class => 'videos',                // queue only (default connection)
]);
```

<Info>
  Queue routing can still be overridden per-dispatch with `onQueue()` or `onConnection()`.
</Info>

### Synchronous dispatch (for testing and development)

Skip the queue and run a job immediately with `dispatchSync`:

```php theme={null}
SendWelcomeEmail::dispatchSync($user);
```

### Bulk dispatching

When you need to dispatch many independent jobs at once without [batch](#job-batches) tracking or callbacks, use the `bulk` method on the `Bus` facade. Laravel groups the jobs by their configured queue connection and queue name, then pushes each group to the appropriate queue in bulk:

```php theme={null}
use App\Jobs\ProcessUser;
use Illuminate\Support\Facades\Bus;

Bus::bulk(
    $users->map(fn ($user) => new ProcessUser($user))
);
```

<Info>
  `Bus::bulk()` sends jobs to the queue in bulk groups. Unlike `Bus::batch()`, it doesn't provide progress tracking or completion callbacks. Use it when you want to efficiently dispatch a large number of simple, independent jobs in one call.
</Info>

## Delayed dispatching

Delay job execution with the `delay` method. Pass a `DateTime` or a duration:

```php theme={null}
// Run 5 minutes from now
SendWelcomeEmail::dispatch($user)->delay(now()->addMinutes(5));
```

`dispatchAfterResponse` runs the job right after Laravel sends the HTTP response to the browser, so the user isn't kept waiting:

```php theme={null}
SendWelcomeEmail::dispatchAfterResponse($user);
```

## Job chaining

Chain jobs so they run sequentially. If one job in the chain fails, subsequent jobs are not run:

```php theme={null}
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:

```php theme={null}
Bus::chain([
    new ProcessPodcast($podcast),
    new ReleasePodcast($podcast),
])->catch(function (Throwable $e) {
    // A job in the chain failed...
})->dispatch();
```

## Job batches

Batching lets you dispatch a collection of jobs and track their collective progress. Start by creating a migration for the `job_batches` table:

```shell theme={null}
php artisan make:batches-table
php artisan migrate
```

Implement the `Batchable` trait in your job class:

```php theme={null}
<?php

namespace 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`:

```php theme={null}
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:

```php theme={null}
$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)
```

## Running the queue worker

Start a worker process with:

```shell theme={null}
php artisan queue:work
```

Target a specific connection or queue:

```shell theme={null}
# Process only the 'emails' queue on Redis
php artisan queue:work redis --queue=emails

# Use the database connection
php artisan queue:work database
```

<Warning>
  `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.
</Warning>

### Worker options

| Option         | Description                                           |
| -------------- | ----------------------------------------------------- |
| `--tries=N`    | Maximum attempts before a job is marked as failed     |
| `--timeout=N`  | Maximum seconds a single job may run                  |
| `--sleep=N`    | Seconds to sleep when the queue is empty (default: 3) |
| `--max-jobs=N` | Stop the worker after processing N jobs               |
| `--max-time=N` | Stop the worker after N seconds                       |
| `--queue=A,B`  | Process queues in priority order                      |

### Setting retries and timeouts on the job class

You can configure retry and timeout behavior directly on the job using PHP attributes:

```php theme={null}
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\Attributes\Tries;
use Illuminate\Queue\Attributes\Timeout;

#[Tries(3)]
#[Timeout(60)]
class SendWelcomeEmail implements ShouldQueue
{
    use Queueable;

    // ...
}
```

## Releasing jobs with middleware

Use the `Release` middleware when a job should return to the queue instead of running under a particular condition.

```php theme={null}
use Illuminate\Queue\Middleware\Release;

/**
 * Get the middleware the job should pass through.
 */
public function middleware(): array
{
    return [
        // Release for 60 seconds when the condition is true.
        Release::when($this->order->isPending(), releaseAfter: 60),
    ];
}
```

`Release::unless()` releases the job when the condition is `false`:

```php theme={null}
return [
    // Release for 60 seconds unless the order has been paid.
    Release::unless($this->order->isPaid(), releaseAfter: 60),
];
```

Use a closure for more complex conditions:

```php theme={null}
return [
    Release::when(function (): bool {
        return ! $this->order->isPaid();
    }, releaseAfter: 60),
];
```

<Warning>
  Releasing a job still increments its attempt count. Configure `#[Tries]` or the `$tries` property appropriately.
</Warning>

## Failed jobs

### Preparing the failed\_jobs table

When a job exceeds its maximum attempt count, Laravel records it in the `failed_jobs` table. Create the table if it doesn't exist:

```shell theme={null}
php artisan make:queue-failed-table
php artisan migrate
```

### Handling failure in the job

Define a `failed` method to run cleanup logic when a job fails:

```php theme={null}
use Throwable;

public function failed(?Throwable $exception): void
{
    // Notify an admin, clean up resources, etc.
}
```

### Stopping retries by exception

Some exceptions indicate that a job should fail immediately rather than be retried. Configure these exception types using `dontRetry` in `bootstrap/app.php`:

```php theme={null}
use App\Exceptions\InvalidPodcastSourceException;
use Illuminate\Foundation\Configuration\Exceptions;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontRetry([
        InvalidPodcastSourceException::class,
    ]);
})
```

For finer control, pass a closure to `dontRetryWhen`. When the closure returns `true`, the job is marked as failed immediately without further retries:

```php theme={null}
use App\Exceptions\PodcastProcessingException;
use Illuminate\Foundation\Configuration\Exceptions;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontRetryWhen(function (PodcastProcessingException $e) {
        return $e->reason() === 'Subscription expired';
    });
})
```

<Tip>
  Use this for exceptions where retrying would never succeed — for example, validation errors, expired subscriptions, or permanent third-party rejections.
</Tip>

### Managing failed jobs

```shell theme={null}
# List all failed jobs
php artisan queue:failed

# Retry a specific job
php artisan queue:retry ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece

# Retry all failed jobs
php artisan queue:retry all

# Delete a specific failed job
php artisan queue:forget ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece

# Delete all failed jobs
php artisan queue:flush
```

## Queue drivers compared

| Driver     | Best for             | Notes                                                 |
| ---------- | -------------------- | ----------------------------------------------------- |
| `sync`     | Local development    | Runs jobs inline, no worker needed                    |
| `database` | Simple setups        | Uses an existing RDBMS; not ideal for high throughput |
| `redis`    | Production workloads | Fast, scalable; requires a Redis server               |
| `sqs`      | AWS environments     | Managed, highly scalable                              |

<Tip>
  For production Redis queues, consider [Laravel Horizon](https://laravel.com/docs/horizon). It provides a real-time dashboard to monitor job throughput, wait times, and failures.
</Tip>

## Example: order confirmation email

<Steps>
  <Step title="Create the job">
    ```shell theme={null}
    php artisan make:job SendOrderConfirmation
    ```
  </Step>

  <Step title="Implement the job logic">
    ```php theme={null}
    <?php

    namespace 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
        }
    }
    ```
  </Step>

  <Step title="Dispatch from the controller">
    ```php theme={null}
    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.');
    }
    ```
  </Step>

  <Step title="Start the worker">
    ```shell theme={null}
    php artisan queue:work --tries=3 --timeout=30
    ```
  </Step>
</Steps>

## Common use cases

<AccordionGroup>
  <Accordion title="When to use queues">
    * Sending email, SMS, or push notifications
    * Resizing or converting images and video
    * Calling slow external APIs or webhooks
    * Generating reports or exporting CSV files
    * Indexing records in a search engine
  </Accordion>

  <Accordion title="Development tip">
    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.

    ```ini theme={null}
    QUEUE_CONNECTION=sync
    ```
  </Accordion>

  <Accordion title="Common commands reference">
    ```shell theme={null}
    # Start the worker
    php artisan queue:work

    # Restart workers after deployment
    php artisan queue:restart

    # List failed jobs
    php artisan queue:failed

    # Retry all failed jobs
    php artisan queue:retry all

    # Delete all failed jobs
    php artisan queue:flush
    ```
  </Accordion>
</AccordionGroup>


## Related topics

- [Laravel Pulse](/en/pulse.md)
- [Laravel Scout](/en/scout.md)
- [Laravel Cloud — A practical guide to Laravel's dedicated PaaS](/en/blog/laravel-cloud.md)
- [laravel/symfony-on-cloud — Running Symfony Apps on Laravel Cloud](/en/blog/symfony-on-cloud-introduction.md)
- [MongoDB](/en/mongodb.md)
