Skip to content

Queue Configuration

Executables support all three of Laravel's queue configuration methods (class-level defaults, named methods, and dispatch-time options) and add a fourth: the configure() hook. These four layers stack in priority order (lowest to highest).

Layer 1: Class-Level Defaults

All Laravel job properties, interfaces, and attributes work directly on executables. The package reads them at build time:

php
class ProcessOrder
{
    use QueueableExecutable;

    public int $tries = 5;
    public int $timeout = 120;
    public string $queue = "orders";
    public int $backoff = 10;
    public string $connection = "redis";
    public int $maxExceptions = 3;
    public bool $failOnTimeout = true;
    public bool $deleteWhenMissingModels = true;

    public function execute(Order $order): void
    {
        /* ... */
    }
}
php
#[WithoutRelations]
#[DeleteWhenMissingModels]
class ProcessOrder implements ShouldBeEncrypted, ShouldBeUnique, ShouldQueueAfterCommit
{
    use QueueableExecutable;

    public function execute(Order $order): void
    {
        /* ... */
    }
}

Interfaces and attributes override properties when they conflict.

Layer 2: The configure() Hook

This layer is unique to executables. A method that receives a QueueableConfig object as its first parameter, followed by execute arguments by name. It gives you a single place to define all queue configuration dynamically, based on the actual input to execute(). All methods on QueueableConfig are chainable:

php
class ProcessOrder
{
    use QueueableExecutable;

    public function configure(QueueableConfig $config, int $amount, string $currency): void
    {
        $config
            ->tries($amount > 1000 ? 5 : 3)
            ->onQueue($currency === "USD" ? "payments-us" : "payments-intl")
            ->onConnection("redis")
            ->timeout(120)
            ->backoff([30, 60, 120])
            ->maxExceptions(3)
            ->failOnTimeout()
            ->deleteWhenMissingModels()
            ->delay(now()->addMinutes(5))
            ->shouldBeEncrypted()
            ->withoutRelations()
            ->displayName("order-{$amount}-{$currency}")
            ->afterCommit();
    }

    public function execute(string $orderId, int $amount, string $currency): void
    {
        // ...
    }
}

The first parameter must be QueueableConfig. After that, configure() only needs to declare the execute-parameters it cares about. The rest are skipped (see Argument Matching).

All QueueableConfig Methods

MethodPurpose
tries(int)Max retry attempts
timeout(int)Job timeout in seconds
backoff(int|array)Backoff in seconds (or array for exponential)
maxExceptions(int)Max exceptions before stopping
delay(array|int|DateTime|DateInterval)Delay before first execution
withoutDelay()Remove any delay
onQueue(string|UnitEnum)Target queue name
onConnection(string|UnitEnum)Target connection
allOnQueue(string|UnitEnum)Queue for job + entire chain
allOnConnection(string|UnitEnum)Connection for job + entire chain
chain(array)Append a job chain
afterCommit()Dispatch after DB transaction commits
beforeCommit()Dispatch immediately (don't wait for commit)
failOnTimeout()Mark as failed on timeout (instead of retry)
deleteWhenMissingModels()Skip job if serialized model is deleted
shouldBeEncrypted()Encrypt job payload
shouldBeUnique()Lock until job completes
shouldBeUniqueUntilProcessing()Lock until processing starts
uniqueId(string|int)Custom unique lock identifier
uniqueFor(int)Unique lock duration in seconds
displayName(string)Custom display name for queue UI
retryUntil(DateTime)Stop retrying after this time
withoutRelations()Don't serialize model relations

Layer 3: Named Methods

These are the same methods you'd define on a Laravel job. The difference: on executables, they also receive execute-arguments by name. Their return value overrides layers 1 and 2.

MethodReturnsPurpose
backoff()int|arrayBackoff in seconds (or array for exponential)
displayName()stringCustom name for queue UI / Horizon
retryUntil()DateTimeInterfaceStop retrying after this time
tries()intMax retry attempts
uniqueFor()intUnique lock duration in seconds
uniqueId()string|intUnique lock identifier
php
public function displayName(Order $order): string
{
    return "Process order #{$order->reference}";
}

public function tries(Order $order): int
{
    return $order->amount > 5000 ? 10 : 3;
}

public function backoff(): array
{
    return [30, 60, 120]; // exponential backoff
}

public function retryUntil(): DateTime
{
    return now()->addHours(6);
}

public function uniqueId(Order $order): string
{
    return "process-{$order->id}";
}

public function uniqueFor(): int
{
    return 300; // 5 minutes
}

Layer 4: Dispatch-Time Fluent API

The caller overrides everything:

php
ProcessOrder::onQueue("high-priority")
    ->withTries(10)
    ->delay(60)
    ->shouldBeEncrypted()
    ->onConnection("redis")
    ->execute($order);

All Fluent Methods

Available on both onQueue() and prepare():

MethodPurpose
onQueue(string|UnitEnum)Target queue name
onConnection(string|UnitEnum)Target connection
delay(array|int|DateTime|DateInterval)Delay before first execution
withoutDelay()Remove any delay
withTries(int)Max retry attempts
withBackoff(int|array)Backoff in seconds (or array for exponential)
withDisplayName(string)Custom name for queue UI
timeout(int)Job timeout in seconds
maxExceptions(int)Max exceptions before stopping
failOnTimeout()Mark as failed on timeout (instead of retry)
deleteWhenMissingModels()Skip job if serialized model is deleted
shouldBeEncrypted()Encrypt job payload
shouldRetryUntil(DateTime)Stop retrying after this time
chain(array)Append a job chain

Available on onQueue() only:

MethodPurpose
allOnQueue(string|UnitEnum)Queue for job + entire chain
allOnConnection(string|UnitEnum)Connection for job + entire chain
afterCommit()Dispatch after DB transaction commits
beforeCommit()Dispatch immediately (don't wait for commit)
shouldBeUnique()Lock until job completes
shouldBeUniqueUntilProcessing()Lock until processing starts
withUniqueId(string|int)Custom unique lock identifier
withUniqueFor(int)Unique lock duration in seconds

For execution modifiers (when(), unless()), see the dedicated page.

Configuration Priority

PrioritySourceExample
1 (lowest)Properties / interfaces / attributespublic int $tries = 3
2configure() hook$config->tries(5)
3Named methodstries(): int
4 (highest)Dispatch-time fluent calls->withTries(10)

Tags, Middleware & Failure Handling

These methods don't participate in the configuration priority above, but they share the same argument matching behavior. Each method only needs to declare the execute-parameters it uses.

tags()

If you use Laravel Horizon, tags are auto-generated from any Eloquent model in your execute arguments (e.g. App\Models\User:1). This is standard Horizon behavior. Define a tags() method to override them.

middleware()

All Laravel queue middleware works unchanged. You can define middleware as a property, a method, or both. They are merged:

php
// Always applied
public array $middleware = [WithoutOverlapping::class];

// $priority matched by name, merged with the property above
public function middleware(string $priority): array
{
    return [new RateLimited("orders-{$priority}")];
}

uniqueVia()

When using unique jobs, Laravel uses the default cache store to acquire the lock. As with Laravel jobs, define a uniqueVia() method to return a different cache store:

php
public function uniqueVia(Order $order): Repository
{
    return Cache::store("redis");
}

failed()

Like Laravel jobs, executables support a failed() method. The difference: beyond the Throwable first parameter, it also receives your execute-arguments by name:

php
class ProcessOrder
{
    use QueueableExecutable;

    public function execute(string $orderId, int $amount): void
    {
        // ...
    }

    public function failed(Throwable $exception, string $orderId, int $amount): void
    {
        Log::error("Order {$orderId} failed (amount: {$amount})", [
            "exception" => $exception->getMessage(),
        ]);

        OrderFailureNotification::send($orderId);
    }
}

The first parameter must be typed as Throwable. You only need to declare the execute-parameters you need.