> ## 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 en jobs

> Leer hoe je met Laravel queues en jobs zware taken zoals het versturen van e-mails en het verwerken van afbeeldingen asynchroon uitvoert.

## Wat is een queue?

In webapplicaties komen taken voor die meerdere seconden duren, zoals het versturen van e-mails, het verkleinen van afbeeldingen of het aanroepen van externe API's.
Als je dit synchroon binnen een HTTP-request doet, moet de gebruiker wachten totdat de response terugkomt.

Met Laravel queues kun je zulke zware taken **asynchroon op de achtergrond uitvoeren**.
De request geeft direct een response terug, terwijl een workerproces de eigenlijke taak apart afhandelt.

<Info>
  Queues ondersteunen meerdere backends, waaronder database, Redis en Amazon SQS.
  In een ontwikkelomgeving kun je de `sync`-driver gebruiken om jobs direct uit te voeren zonder queue.
</Info>

```mermaid theme={null}
flowchart LR
    A["Job aanmaken<br>dispatch()"] --> B["Queue-driver<br>(Redis/DB enz.)"]
    B --> C["Worker<br>queue:work"]
    C --> D{"Uitvoering geslaagd?"}
    D -->|"Yes"| E["Voltooid"]
    D -->|"No"| F{"Retry mogelijk?"}
    F -->|"Yes"| B
    F -->|"No"| G["Mislukking vastgelegd<br>failed_jobs"]
```

## Queue-configuratie

### config/queue.php

De queue-configuratie staat gebundeld in `config/queue.php`.
Met de omgevingsvariabele `QUEUE_CONNECTION` schakel je tussen drivers.

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

### .env instellen

```ini theme={null}
# Driver kiezen
QUEUE_CONNECTION=database

# Bij gebruik van Redis
# QUEUE_CONNECTION=redis
# REDIS_HOST=127.0.0.1
# REDIS_PORT=6379
```

### De databasedriver voorbereiden

Als je de `database`-driver gebruikt, heb je een tabel nodig om jobs in op te slaan.
Nieuwe projecten vanaf Laravel 11 bevatten de migration standaard,
maar als die ontbreekt maak je hem aan met de volgende commando's.

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

### De Redis-driver voorbereiden

Gebruik je de `redis`-driver, voeg dan een Redis-verbindingsconfiguratie toe aan `config/database.php`
en installeer de driver via Composer.

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

### SQS Overflow Storage

Amazon SQS heeft een limiet op de maximale grootte van message payloads.
Werk je met jobs waarvan de payload groot kan worden, voeg dan een configuratie toe die het overschot in een cache store opslaat en alleen een pointer naar SQS stuurt.

```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),
    ],
],
```

* Als je `enabled` aanzet, worden payloads van **1 MB of groter** opgeslagen in de opgegeven cache store.
* Zet je `always` op `true`, dan worden alle SQS-payloads ongeacht hun grootte in de cache store opgeslagen.
* `delete_after_processing` verwijdert de opgeslagen payload nadat de job geslaagd is (standaard `true`).
* Zet je `flush_on_clear` op `true`, dan wordt bij het uitvoeren van `queue:clear` een `flush` gedaan op de overflow-store. Gebruik dit in combinatie met een aparte store, zodat je de reguliere cache niet wist.

## Een jobklasse maken

### Het make:job commando

Met het Artisan-commando `make:job` genereer je een sjabloon voor een jobklasse.

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

Het bestand `app/Jobs/SendWelcomeEmail.php` wordt gegenereerd.

### Structuur van een jobklasse

```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;

    /**
     * Maak een instantie van de job
     */
    public function __construct(
        public User $user,
    ) {}

    /**
     * Voer de job uit
     */
    public function handle(): void
    {
        Mail::to($this->user->email)->send(new WelcomeMail($this->user));
    }
}
```

Door de interface `ShouldQueue` te implementeren laat je Laravel weten dat deze job asynchroon via de queue verwerkt moet worden.
De trait `Queueable` levert de methoden die nodig zijn voor de queue-bewerkingen van de job.

<Tip>
  Geef je een Eloquent-model door aan de constructor, dan serialiseert Laravel automatisch alleen het ID.
  Bij het uitvoeren worden de meest recente gegevens opnieuw uit de database opgehaald, waardoor de queue-payload licht blijft.
</Tip>

## Jobs dispatchen

### dispatch()

Om een job vanuit een controller of service naar de queue te sturen gebruik je `dispatch()`.

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

// In een route of controller
public function register(Request $request): RedirectResponse
{
    $user = User::create($request->validated());

    // Job op de queue plaatsen
    SendWelcomeEmail::dispatch($user);

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

### Vertraagd dispatchen

Met de methode `delay()` kun je de uitvoering van een job een opgegeven tijd uitstellen.

```php theme={null}
// Na 5 minuten uitvoeren
SendWelcomeEmail::dispatch($user)->delay(now()->addMinutes(5));
```

### dispatchAfterResponse()

Met `dispatchAfterResponse()` wordt de job **direct nadat** de HTTP-response naar de gebruiker is teruggestuurd uitgevoerd.
Dit werkt ook met de `sync`-driver en is daarom geschikt voor lichte taken waarvoor geen aparte worker nodig is.

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

### Dispatchen naar een specifieke queue

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

### Queue Routing

Om een specifieke jobklasse standaard naar een vaste verbinding en queue te routeren, gebruik je `Queue::route()` in de `boot()` van een ServiceProvider. Zo beheer je alles centraal in plaats van per jobklasse `onQueue()` / `onConnection()` te schrijven.

```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');
}
```

Je kunt ook interfaces, traits of parent classes opgeven. De routing wordt dan automatisch toegepast op alle jobs die deze implementeren, gebruiken of erven.

Om meerdere jobs in één keer te routeren geef je een array door.

```php theme={null}
Queue::route([
    ProcessPodcast::class => ['redis', 'podcasts'], // connection en queue
    ProcessVideo::class => 'videos',                // alleen queue (standaardverbinding wordt gebruikt)
]);
```

<Info>
  Queue Routing kan worden overschreven met `onQueue()` / `onConnection()` op de job zelf.
</Info>

### Queues doorsturen (`Queue::forward()`)

Met `Queue::forward()` kun je jobs van de ene queue doorsturen naar een andere queue of verbinding. Handig als je de queue-infrastructuur wilt wisselen zonder de individuele jobs of de aanroepende code aan te passen.

```php theme={null}
use Illuminate\Support\Facades\Queue;

Queue::forward('reports', 'reports.fifo', 'sqs');
Queue::forward('payments', connection: 'sqs');
Queue::forward('updates', 'notifications');
```

Om meerdere queues in één keer door te sturen geef je een array door.

```php theme={null}
Queue::forward([
    'reports' => 'reports.fifo',
    'emails' => 'emails.fifo',
], connection: 'sqs');
```

<Info>
  Als de job zelf expliciet een verbinding opgeeft, heeft de instelling van de job voorrang op de doorstuurconfiguratie.
</Info>

### Synchroon uitvoeren (voor testen en ontwikkeling)

Met `dispatchSync()` voer je een job direct uit zonder de queue te gebruiken.

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

### Bulk dispatchen

Als je een groot aantal onafhankelijke jobs in één keer wilt dispatchen, kun je de methode `bulk()` van de `Bus`-facade gebruiken. Ideaal voor gevallen waarin je geen tracking of callbacks zoals bij batchverwerking nodig hebt.

`Bus::bulk()` groepeert jobs op de geconfigureerde queue-verbinding en queuenaam en pusht elke groep in één keer naar de queue, wat efficiënt is.

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

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

<Info>
  `Bus::bulk()` verstuurt jobs **gebundeld** naar de queue. Anders dan bij batchverwerking (`Bus::batch()`) krijg je geen voortgangstracking of voltooiingscallbacks. Het is geschikt als je een grote hoeveelheid onafhankelijke jobs simpelweg in één keer wilt versturen.
</Info>

### Job chains

Door jobs te chainen kun je meerdere jobs na elkaar uitvoeren. Als een job in de chain mislukt, worden de volgende jobs niet uitgevoerd.

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

Bus::chain([
    new ProcessPodcast($podcast),
    new OptimizePodcast($podcast),
    new ReleasePodcast($podcast),
])->dispatch();
```

Je kunt ook callbacks toevoegen die worden uitgevoerd wanneer de hele chain voltooid is of wanneer een job in de chain mislukt.

```php theme={null}
Bus::chain([
    new ProcessPodcast($podcast),
    new ReleasePodcast($podcast),
])->catch(function (Throwable $e) {
    // Afhandeling wanneer een job in de chain mislukt
})->dispatch();
```

### Job batches

Met batches kun je meerdere jobs gebundeld dispatchen en de totale voortgang volgen. Maak eerst een migration voor de tabel `job_batches`.

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

Gebruik de trait `Batchable` in je jobklasse.

```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;
        }

        // Een chunk contacten importeren...
    }
}
```

Met `Bus::batch()` dispatch je een batch en kun je callbacks registreren voor voltooiing, mislukking en afronding.

```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) {
    // Wanneer alle jobs succesvol zijn voltooid
})->catch(function (Batch $batch, Throwable $e) {
    // Wanneer een job mislukt
})->finally(function (Batch $batch) {
    // Wanneer de uitvoering van de batch klaar is
})->dispatch();
```

Met het batch-ID kun je de status van de batch opvragen.

```php theme={null}
$batch = Bus::findBatch($batchId);

$batch->totalJobs;      // Totaal aantal jobs
$batch->pendingJobs;    // Aantal wachtende jobs
$batch->failedJobs;     // Aantal mislukte jobs
$batch->progress();     // Voortgangspercentage (0–100)
```

## Jobs verwerken

### Het queue:work commando

Start een queue-worker om jobs te verwerken.

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

Je kunt ook een specifieke driver of queue opgeven.

```shell theme={null}
# Alleen de emails-queue van Redis verwerken
php artisan queue:work redis --queue=emails

# De databasedriver gebruiken
php artisan queue:work database
```

<Warning>
  `queue:work` blijft na het starten draaien. Herstart de worker met `queue:restart` wanneer je de code hebt aangepast.
  In productie is het gebruikelijk om workers te beheren met een procesmanager zoals Supervisor.
</Warning>

## Opties voor het beheren van queue-workers

Door veelgebruikte opties te combineren kun je de worker nauwkeurig aansturen.

```shell theme={null}
php artisan queue:work --tries=3 --timeout=60 --sleep=3
```

| Optie          | Beschrijving                                                                                        |
| -------------- | --------------------------------------------------------------------------------------------------- |
| `--tries=N`    | Maximaal aantal pogingen per job. Daarna wordt de job als mislukt geregistreerd                     |
| `--timeout=N`  | Maximale uitvoeringstijd in seconden per job. Bij overschrijding wordt de worker geforceerd gestopt |
| `--sleep=N`    | Aantal seconden wachten tot de volgende poll wanneer de queue leeg is (standaard: 3)                |
| `--max-jobs=N` | Beëindig de worker na het verwerken van N jobs                                                      |
| `--max-time=N` | Beëindig de worker na N seconden                                                                    |
| `--queue=A,B`  | Verwerk queues met prioriteit (A heeft voorrang)                                                    |

### Retry-instellingen in de jobklasse zetten

Soms is het overzichtelijker om de instellingen in de jobklasse zelf te zetten in plaats van via commandolijnopties.

```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;

    // ...
}
```

## Jobs releasen (Release middleware)

Wil je een job onder bepaalde voorwaarden niet uitvoeren maar terugzetten op de queue, dan schrijf je dat beknopt met de `Release`-middleware.

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

/**
 * Geef de middleware terug waar de job doorheen gaat
 */
public function middleware(): array
{
    return [
        // Release na 60 seconden wanneer $condition true is
        Release::when($this->order->isPending(), releaseAfter: 60),
    ];
}
```

`Release::unless()` releaset wanneer de voorwaarde `false` is.

```php theme={null}
return [
    // Release na 60 seconden wanneer de bestelling nog niet betaald is
    Release::unless($this->order->isPaid(), releaseAfter: 60),
];
```

Met een closure kun je complexere voorwaarden beschrijven.

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

<Warning>
  Ook bij het releasen van een job wordt het aantal pogingen opgehoogd. Stel `#[Tries]` of de property `$tries` passend in.
</Warning>

## Mislukte jobs afhandelen

### De failed\_jobs-tabel voorbereiden

Wanneer een job het maximale aantal pogingen overschrijdt, wordt hij vastgelegd in de tabel `failed_jobs`.
Bestaat de tabel nog niet, maak hem dan aan met de volgende commando's.

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

### Opruimen bij mislukking

Definieer je een methode `failed()` op de job, dan kun je daar de afhandeling na een mislukking beschrijven.

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

public function failed(?Throwable $exception): void
{
    // Bijvoorbeeld een Slack-notificatie naar de beheerder sturen
    // Notification::route('slack', config('app.slack_webhook'))
    //     ->notify(new JobFailedNotification($this, $exception));
}
```

### Retries stoppen op basis van exceptions

Bij bepaalde soorten exceptions wil je de job niet opnieuw laten proberen maar direct laten mislukken. Geef binnen `withExceptions()` in `bootstrap/app.php` met `dontRetry` de betreffende exception classes op.

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

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

Heb je fijnmazigere controle nodig, geef dan een closure door aan `dontRetryWhen`. Wanneer de closure `true` teruggeeft, wordt de job direct als mislukt gemarkeerd en niet opnieuw geprobeerd.

```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>
  Bij exceptions waarvan het resultaat niet verandert hoe vaak je ook retryt — zoals validatiefouten of betalingsfouten (bijvoorbeeld een verlopen abonnement) — is het efficiënt om de job op deze manier direct te laten mislukken.
</Tip>

### Overzicht van mislukte jobs bekijken

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

### Mislukte jobs opnieuw proberen

```shell theme={null}
# Een specifiek job-ID opnieuw proberen
php artisan queue:retry ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece

# Alle mislukte jobs opnieuw proberen
php artisan queue:retry all
```

### Mislukte jobs verwijderen

```shell theme={null}
# Een specifieke job verwijderen
php artisan queue:forget ce7bb17c-cdd8-41f0-a8ec-7b4fef4e5ece

# Alle mislukte jobs verwijderen
php artisan queue:flush
```

## Veelgebruikte queue-drivers

### De database-driver

Een eenvoudige driver waarmee je zonder extra middleware aan de slag kunt.
Jobs worden opgeslagen in de tabel `jobs` en workers pollen deze om ze te verwerken.

* **Voordelen**: eenvoudige setup, je kunt je bestaande RDBMS direct gebruiken
* **Nadelen**: hoge belasting op de database, dus minder geschikt voor grote aantallen jobs

```ini theme={null}
QUEUE_CONNECTION=database
```

### De redis-driver

De snelle driver die in productieomgevingen het meest wordt gebruikt.
Omdat hij in-memory werkt is de doorvoer hoger dan bij een database en kun je grote aantallen jobs verwerken.

* **Voordelen**: snel, schaalbaar
* **Nadelen**: je hebt een Redis-server nodig

```ini theme={null}
QUEUE_CONNECTION=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
```

<Tip>
  Draai je Redis-queues in productie, overweeg dan [Laravel Horizon](https://laravel.com/docs/horizon) te gebruiken.
  Met een fraai dashboard kun je de status van jobs in realtime monitoren.
</Tip>

## Productiegebruik met Supervisor

In productie heb je een mechanisme nodig dat het `queue:work`-proces automatisch herstart wanneer het om welke reden dan ook stopt.
Op Linux is het gebruikelijk om hiervoor **Supervisor** te gebruiken.

```ini theme={null}
# /etc/supervisor/conf.d/laravel-worker.conf
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/your-app/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/your-app/storage/logs/worker.log
stopwaitsecs=3600
```

Met `numprocs=2` start je twee workerprocessen parallel.
Herlaad Supervisor na het instellen.

```shell theme={null}
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*
```

## Praktijkvoorbeeld: e-mails versturen via de queue

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

  <Step title="Implementeer de verwerking van de job">
    ```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));
        }
    }
    ```
  </Step>

  <Step title="Dispatch vanuit de 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', 'Je bestelling is ontvangen.');
    }
    ```
  </Step>

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

## Samenvatting

<AccordionGroup>
  <Accordion title="Wanneer je queues zou moeten gebruiken">
    * E-mails en sms'jes versturen
    * Afbeeldingen en video's verkleinen of converteren
    * Requests naar externe API's
    * Rapporten genereren of CSV-exports maken
    * Webhooks versturen
  </Accordion>

  <Accordion title="Tips voor tijdens de ontwikkeling">
    Zet je in `.env` `QUEUE_CONNECTION=sync`, dan worden jobs direct uitgevoerd zonder de queue te gebruiken.
    Je kunt dan alles testen zonder een worker te starten, wat tijdens de ontwikkeling handig is.

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

  <Accordion title="Overzicht van veelgebruikte commando's">
    ```shell theme={null}
    # Worker starten
    php artisan queue:work

    # Worker herstarten (na een deploy)
    php artisan queue:restart

    # Overzicht van mislukte jobs
    php artisan queue:failed

    # Alle mislukte jobs opnieuw proberen
    php artisan queue:retry all

    # Alle mislukte jobs verwijderen
    php artisan queue:flush
    ```
  </Accordion>
</AccordionGroup>


## Related topics

- [Events en listeners](/nl/events.md)
- [Laravel-updates van juli 2026](/nl/blog/changelog/202607.md)
- [Concurrency](/nl/concurrency.md)
- [Laravel Horizon](/nl/horizon.md)
- [Broadcasting](/nl/broadcasting.md)
