Laravel ships a release most weeks, each with a few hundred changes, and the handful that decide how your background jobs behave are scattered among them. This page is every one of those, from 13.0.0 onwards — assembled from the framework's own history by what each commit touched, not by what its subject line happened to say.
What changed in Laravel 13 queues#
-
Queue routing.
Queue::route(ProcessPodcast::class, 'podcasts', 'redis')declares which connection and queue a job class defaults to, centrally, instead of at every dispatch site. -
Job settings became attributes. Twelve of them by 13.33 —
#[Tries],#[Backoff],#[Timeout],#[FailOnTimeout],#[UniqueFor]and the rest — including#[DebounceFor], which is new behaviour rather than a restatement. -
The queue will tell you what is in it. Pending, delayed and reserved counts, the payloads
themselves as collections, a single
totalSize(), and the creation time of the oldest pending job. Four of those are on theQueuecontract, which is what breaks custom drivers. - Pausing grew up. A global switch, a pause with a TTL, events for workers entering and leaving the paused state, and worker output that no longer makes a paused queue look like an empty one.
-
Twelve more queue events, nineteen at the end of 12.x against thirty-one now. Most
usefully
UniqueJobSkipped, which makes a droppedShouldBeUniquedispatch visible for the first time.
What breaks on the way from Laravel 12#
The queue section of the upgrade guide is marked low impact, and for most applications it is. It is not low impact if you maintain a custom queue driver, or if you listen to queue events — which, if you alert on backlog, you do.
| Change | In | What to do |
|---|---|---|
| JobAttempted carries the exception, not a boolean | 13.0.0 | Read the note |
| The Queue contract gained four size methods | 13.0.0 | Read the note |
| QueueBusy::$connection renamed to $connectionName | 13.0.0 | Read the note |
The changes that change how you run queues#
Most of the 213 entries below are one line because one line is all they need. These are the ones that are worth reading properly.
Pausing a queue became a real operation#
More of this cycle went into pausing than into anything else.
Laravel 12.40 introduced per-queue pausing; 13.x turned it into
something you would reach for during an incident. There is a global
switch now — queue:pause --all,
Queue::pauseAll() — a pause with a TTL so it expires
on its own, events for workers entering and leaving the paused
state, and worker output that says which queues are paused instead
of looking identical to an empty queue.
#61126, #61142, #59895, #61139
The framework will now tell you what is in a queue#
Laravel 13 went from size() to a full inspection API:
pending, delayed and reserved counts per queue, the same across a
whole connection, a single totalSize(), the payloads
themselves as collections, and the creation time of the oldest
pending job — the one number that can distinguish a busy queue from
a stuck one.
This is the change with the widest blast radius in the major,
because the four new methods are on the
Queue contract. Every custom driver has to implement
them.
#59997, #61231, #61373, #58914
Job configuration moved to attributes#
Twelve attributes now cover what used to be properties and
methods — #[Tries], #[Backoff],
#[Timeout], #[FailOnTimeout],
#[UniqueFor] and the rest — plus
#[DebounceFor], which is genuinely new behaviour rather
than a restatement: a job that collapses a burst of dispatches into
one run after the burst ends.
A worker that says what it is doing#
Illuminate\Queue\Events held nineteen classes at the
end of the 12.x line and holds thirty-one in 13.33. The ones that change what you can observe:
WorkerIdle for a poll that found nothing,
JobReleased and JobInterrupted for the two
ways a job ends without finishing, a reason and a job count on
WorkerStopping, and
UniqueJobSkipped — which finally makes a dropped unique
dispatch visible instead of silent. Jobs can also see the signals
sent to their worker and wind themselves down rather than being
killed mid-flight.
#60134, #61108, #61412, #58341, #59833, #61039
Routing, forwarding, and moving work without a deploy#
Queue::route() declares which queue a job class belongs
on in one place rather than at every dispatch site, and
Queue::forward() redirects an entire queue's traffic
elsewhere at runtime. Both exist for the same reason: moving work
between queues used to be a code change, and incidents do not wait
for a deploy.
What this page counts as a queue change#
An entry is here if the commit behind it touched a file that decides how background work behaves:
Illuminate\Queue, Illuminate\Bus, the queue and bus contracts, the
queue:* commands, the queue fakes — plus the queue-aware files elsewhere, such as the queued
mailable that was being pushed to the wrong queue when its name was a backed enum. Matching on paths rather
than on wording is what catches a change whose subject line never says "queue".
Deliberately outside it: the scheduler, which is not the queue however much
withoutOverlapping() smells like one; broadcasting, where BroadcastEvent genuinely is a
queued job but including it would drag in every websocket change for the sake of a handful; and the cache, which
backs the unique and overlapping locks without being part of the queue.
Rebuilt from laravel/framework's git history, release by release. Every entry links to the pull request, or to the commit for the handful of changes pushed straight to the branch. The notes are written by hand; the list is not.
Every change, by release
| Version | Released | Changes | Most notable |
|---|---|---|---|
| 13.33.0 | 22 Sep 2026 | 6 | queue:retry-batch skips bad batch IDs and exits non-zero |
| 13.32.0 | 15 Sep 2026 | 5 | Enums accepted by the pause and resume methods |
| 13.31.0 | 8 Sep 2026 | 6 | The Redis driver is cluster-safe |
| 13.30.1 | 1 Sep 2026 | 1 | connection and queue on WorkerStopping |
| 13.30.0 | 1 Sep 2026 | 4 | Add totalXSize methods to Cloud Queue |
| 13.27.0 | 25 Aug 2026 | 4 | The after-commit rollback path stopped releasing locks it never took |
| 13.26.0 | 18 Aug 2026 | 10 | Queue::forward(): move everything on one queue to another |
| 13.25.0 | 11 Aug 2026 | 8 | A global pause switch: queue:pause --all |
| 13.24.0 | 4 Aug 2026 | 13 | Unique locks became owner-checked |
| 13.23.0 | 27 Jul 2026 | 1 | Reverted the 13.22 ShouldBeUniqueUntilProcessing lock change |
| 13.22.0 | 24 Jul 2026 | 5 | add the exception to JobReleasedAfterException |
| 13.21.0 | 21 Jul 2026 | 5 | InvalidPayloadException names the job and the queue |
| 13.20.0 | 14 Jul 2026 | 6 | Fix SQS queue size null coalescing precedence |
| 13.19.0 | 7 Jul 2026 | 6 | Bulk SQS dispatch uses SendMessageBatch |
| 13.18.1 | 2 Jul 2026 | 2 | Support inspecting delayed jobs on the queue fake |
| 13.18.0 | 30 Jun 2026 | 4 | WorkerStopping exposes the jobs-processed count and last job timestamp |
| 13.17.0 | 23 Jun 2026 | 2 | Should Not Retry Exception Handler |
| 13.16.0 | 16 Jun 2026 | 4 | Add missing @throws \JsonException annotations |
| 13.15.0 | 9 Jun 2026 | 14 | Allow enums in Queue::route |
| 13.14.0 | 4 Jun 2026 | 2 | Add queue to InspectedJob |
| 13.13.0 | 2 Jun 2026 | 5 | Bus::dispatchBulk() |
| 13.12.0 | 26 May 2026 | 6 | Update Worker.php |
| 13.11.0 | 19 May 2026 | 1 | Dedicated Cloud Queue |
| 13.10.0 | 19 May 2026 | 10 | WorkerIdle: the event for a worker with nothing to do |
| 13.9.0 | 13 May 2026 | 13 | SQS payloads larger than 256KB can spill to disk |
| 13.8.0 | 5 May 2026 | 6 | Reading the jobs inside a queue, from the framework |
| 13.7.0 | 28 Apr 2026 | 9 | Jobs can react to the signals sent to their worker |
| 13.6.0 | 21 Apr 2026 | 7 | Debounceable jobs: #[DebounceFor] |
| 13.5.0 | 14 Apr 2026 | 6 | Support #[Delay] attribute on queued mailables |
| 13.4.0 | 7 Apr 2026 | 5 | Add queue methods to inspect jobs |
| 13.3.0 | 1 Apr 2026 | 9 | Add BatchStarted event |
| 13.2.0 | 24 Mar 2026 | 5 | Allow opting out of worker Job exception reporting |
| 13.1.1 | 18 Mar 2026 | 1 | Break queue dependency |
| 13.1.0 | 18 Mar 2026 | 2 | Use Carbon::now() and Date::now() instead of now() helper |
| 13.0.0 | 17 Mar 2026 | 20 | JobAttempted carries the exception, not a boolean |
7 releases in this range changed nothing about the queue and so appear nowhere below: 13.29.0, 13.28.0, 13.26.1, 13.21.1, 13.16.1, 13.11.2, 13.11.1. Named here so that a version missing from the table reads as “nothing to report” rather than as a gap in the scan.
Laravel 13.33.0 22 Sep 2026 · 6 changes
-
Fix queue:retry-batch skips bad batch IDs and exits non-zero #61653 #
Upstream: “Skip batches that cannot be retried in queue:retry-batch”
An ID for a batch that did not exist printed an error and then carried on into the batch's failed jobs anyway, which crashed the command on a null batch and abandoned any IDs after it. A batch with no failed jobs printed an error and then did nothing. Either way the command exited 0. Now it skips the bad ID, gets on with the rest, and exits 1 if any of them were skipped. A deploy script that runs
queue:retry-batchfor whatever IDs it has can now fail where it used to pass. -
Fix BatchFinished and BatchCanceled carry the batch as stored, not a stale copy #61644 #
Upstream: “Pass fresh batch to `BatchFinished` and `BatchCanceled` events”
Both events were dispatched with the in-memory
Batchthe last job was holding, loaded before the repository marked it finished or cancelled. ABatchFinishedlistener got a batch whosefinished()was false and whosefinishedAtwas null, and aBatchCanceledlistener got one whosecancelled()was false. The batch is now reloaded before dispatch. If a listener was re-fetching the batch to work around this, it can stop. -
Feature Worker::killUsing(): the timed-out exit code finally reaches the supervisor #61622 #
Upstream: “Make the worker timed out exit code reachable”
Worker::$timedOutExitCodehas been settable for years and has never done anything.kill()sends the process an uncatchableSIGKILLjust before itsexit($status), so the status never leaves: whatever is watching the worker sees a signal death, the same one it sees for an out-of-memory kill.Worker::killUsing(fn (int $status) => …)registers a callback that runs before theSIGKILLand can end the process some other way. Laravel Cloud uses it topcntl_exec()intosh -c 'exit 124'— same PID, no shutdown functions or destructors, like the kill, but the parent gets a real exit code. If the callback returns, theSIGKILLhappens as before. If you run workers under supervisord or Kubernetes, this is what lets a restart log tell a timeout apart from memory exhaustion. -
Feature Worker::$killOnTimeout: a timed-out job no longer has to take the worker with it #61591 #
Upstream: “Ability to opt out of killing the worker on job timeout”
A job that ran past its timeout has always cost you the whole worker: the
SIGALRMhandler fails or releases the job, firesJobTimedOut, thenSIGKILLs its own process and leaves the supervisor to start a new one. SetWorker::$killOnTimeout = falsein a service provider and the handler throws theTimeoutExceededExceptioninto the running job instead. The job takes the normal exception path — released or failed according to its tries — and the same process moves on to the next one.The kill stays the default for a reason. The exception lands wherever the job happened to be, so a job that catches
Throwableswallows its own timeout, and whatever it left half-done — an open transaction, a held lock — is now inherited by the next job instead of dying with the process. -
Change Timeout handler drops a redundant pcntl_signal() argument (no behaviour change) #61590 #
Upstream: “Update Worker.php”
-
Change Laravel Cloud: queue:retry pages through failed jobs, with backoff #61584 #
Upstream: “Improve Cloud job retries”
Laravel 13.32.0 15 Sep 2026 · 5 changes
-
Feature Enums accepted by the pause and resume methods #61464 #
Upstream: “Allow enums in queue pause/resume methods”
-
Change assign message group id when using fifo 5c6b069aab23 #
Laravel 13.31.0 8 Sep 2026 · 6 changes
-
Fix Fix RateLimited job middleware hitting limits that did not block the job #61449 #
-
Feature JobInterrupted #61412 #
Upstream: “`JobInterrupted` event”
-
Change Include connection and queue on WorkerStopping when worker is killed #61408 #
-
Feature totalSize(): everything queued on a connection, in one number #61373 #
Upstream: “Add totalSize method to Queue”
-
Fix The Redis driver is cluster-safe #61198 #
Upstream: “Make the Redis queue driver cluster-safe (`bulk()` node-less `MULTI`; `allQueueNames()` uses `KEYS`)”
Two problems on Redis Cluster.
bulk()wrapped its pushes in aMULTIthat could span nodes, which a cluster refuses — so batch dispatch failed outright on some cluster setups. AndallQueueNames()now usesKEYS, which is cluster-routable where the previous approach was not.KEYSis worth knowing about: it is O(n) over the keyspace and blocks the server for the duration. It runs on the inspection path rather than the pop path, so it will not slow your workers, but a dashboard polling it against a large keyspace is a different matter. -
Change reorder args ce2511a6ab5e #
Laravel 13.30.1 1 Sep 2026 · 1 change
Laravel 13.30.0 1 Sep 2026 · 4 changes
Laravel 13.27.0 25 Aug 2026 · 4 changes
-
Change Share cached AWS credentials across processes for SQS queue connections #61321 #
-
Fix The debounce max-wait window restarts after a run #61281 #
Upstream: “Start a new max wait window after a debounced job runs”
-
Fix The after-commit rollback path stopped releasing locks it never took #61234 #
Upstream: “Don't force release a unique lock the job never acquired”
The narrow follow-up to the owner check in 13.24. A
ShouldBeUniquejob dispatched inside a transaction registers a rollback callback that releases its lock if the transaction never commits — and that callback was still releasing unconditionally, so a dispatch that had found the lock already held would, on rollback, free somebody else's. It now returns early when no owner token was recorded. -
Feature allPendingSize, allDelayedSize and allReservedSize #61231 #
Upstream: “Add Queue `allXSize` methods”
Laravel 13.26.0 18 Aug 2026 · 10 changes
-
Change Promote nested data to top-level managed queue event #61225 also 12.x #
-
Change Promote nested data to top-level managed queue event #61209 #
-
Feature Queue::forward(): move everything on one queue to another #61188 #
Upstream: “Introduce `Queue::forward()`”
Queue::forward('reports', 'reports-slow')redirects a queue's traffic to another queue, optionally on another connection. The operational case is a queue that has become a problem — one tenant's jobs swamping a shared queue, or work that needs to be drained somewhere with different worker settings — where the alternative was a deploy. -
Change Retry timed-out cloud agent long-polls #61178 also 12.x #
-
Feature Queued event listeners can be debounced too #61169 #
Upstream: “Add debounceable queued listeners”
-
Change queue:work prints which queues are paused #61142 #
Upstream: “Surface paused queues in the queue worker output”
A paused queue and an empty queue produced identical worker output, which made a forgotten pause one of the quieter ways to lose a day. The worker now says so, and fires
WorkerQueuePausedandWorkerQueueResumedas it notices. -
Performance Paused-queue checks no longer read across cluster slots #61139 #
Upstream: “Avoid cross-slot reads when checking paused queues”
The pause check ran on every poll of every queue, and on Redis Cluster its keys could land in different slots, making it a cross-slot read on the hot path. Part of the same run of work as the cluster-safety fixes in 13.31.
-
Feature JobReleased fires from the worker #61108 #
Upstream: “Add JobReleased event to Worker”
Laravel 13.25.0 11 Aug 2026 · 8 changes
-
Feature A global pause switch: queue:pause --all #61126 #
Upstream: “Add a global pause switch for queues”
Queue::pauseAll()andQueue::resumeAll(), andphp artisan queue:pause --allin front of them, stop every queue on every connection rather than one named queue at a time. This is the switch you want during a database failover or a bad deploy, where the alternative was pausing queues one by one or stopping the workers and losing the reservations they held.Paused means workers keep running and keep polling but pop nothing, so the backlog builds in Redis rather than in a killed process's memory. 13.10 also added
pauseFor(), which takes a TTL so a pause expires on its own instead of outliving the incident and the person who declared it. -
Fix Backed enum queue names work for queued mailables #61066 #
Upstream: “Respect backed enum queue names when queueing mailables”
Queue names as enums are supported across the queue API, but a mailable queued with one was pushed to a queue named after the enum case object rather than its value — so the mail sat on a queue no worker was listening to, with nothing logged. Worth checking if you moved your queue names to an enum during this cycle.
-
Feature JobTimedOut carries the timeout it exceeded #61060 #
Upstream: “add the timeout to job timedout”
-
Feature UniqueJobSkipped: the silent no-op finally fires an event #61039 #
Upstream: “Add a UniqueJobSkipped event”
A
ShouldBeUniquedispatch that finds the lock held is dropped with no exception, no log line and no failed job — the job simply never appears anywhere, which is the single most confusing behaviour in Laravel's queue.UniqueJobSkippedmakes it observable: listen for it and you can count, log or alert on dispatches that went nowhere.It fires on the
dispatch()path, which is also the only path that takes the lock —Bus::batch(),Queue::push()andqueue:retryskip uniqueness entirely, so they have nothing to report. -
Fix Bulk pushes to the database driver respect after-commit dispatch #60996 #
Upstream: “Respect after-commit dispatch when bulk pushing to DatabaseQueue”
-
Feature Add uuid to queue fake for inspection methods #60966 #
Laravel 13.24.0 4 Aug 2026 · 13 changes
-
Change Revert "Use Str::uuid()->toString() instead of (string) casts (#60995)" #61028 #
-
Change Use strict comparisons for int-to-int checks #61005 #
-
Change Use reduce() instead of foreach accumulator loops #61004 #
-
Change Use Str::uuid()->toString() instead of (string) casts #60995 #
-
Change Sync property docblocks with their parent declarations #60991 #
-
Change Use Stringable::explode() instead of wrapping explode() in a Collection #60979 #
-
Change Prefer isEmpty()/isNotEmpty()/contains() over count() checks #60978 #
-
Change Respect job delays when bulk pushing to FailoverQueue #60950 #
-
Change Use self::SUCCESS/self::FAILURE in console commands #60928 #
-
Change Consolidate Artisan command $name/getArguments()/getOptions() into $signature #60926 #
-
Change Respect job delays when bulk pushing to QueueFake #60916 #
-
Change Use immutable timestamps in batch testing fakes #60912 #
-
Fix Unique locks became owner-checked #60906 #
Upstream: “Attempt at lock fix”
Releasing a unique lock was unconditional: whoever called release cleared the
laravel_unique_job:key for that unique ID, whether or not they were the dispatch that acquired it. WithShouldBeUniqueUntilProcessing, where a second copy can legitimately be queued while the first runs, the first copy finishing released the second copy's lock — and the next dispatch queued a duplicate.UniqueLock::acquire()now stores the lock's owner token on the job asuniqueLockOwnerand the release checks it. Two conditions apply, and both are quiet: the job has to use theQueueabletrait, and the cache store has to implementLockProvider. On a store that does not — the array store, or a custom one — nothing is recorded and the old behaviour stands.
Laravel 13.23.0 27 Jul 2026 · 1 change
-
Fix Reverted the 13.22 ShouldBeUniqueUntilProcessing lock change #60905 #
Upstream: “Revert "Fix ShouldBeUniqueUntilProcessing retries releasing locks they don't …"”
Worth seeing as one story rather than four entries: 13.22 tried to stop retried
ShouldBeUniqueUntilProcessingjobs releasing locks they did not own, 13.23 reverted it, 13.24 solved it differently with an owner token on the lock, and 13.27 closed the after-commit rollback path it had missed. If you pinned below 13.24 during that run, this is the arc you were avoiding.
Laravel 13.22.0 24 Jul 2026 · 5 changes
-
Feature Add multi-queue support to queue:clear command #60873 #
-
Feature add the exception to JobReleasedAfterException #60823 #
-
Feature The #[Delay] attribute is honoured by Bus::batch() and bulk pushes #60766 #
Upstream: “Support `Delay` attribute when using `Bus::batch` & bulk”
-
Change adjust queue fake to handle created at and creationTimeOfOldestPendingJob #60730 #
Laravel 13.21.0 21 Jul 2026 · 5 changes
Laravel 13.20.0 14 Jul 2026 · 6 changes
Laravel 13.19.0 7 Jul 2026 · 6 changes
-
Deprecation Remove deprecated `StaticCallOnNonStaticToInstanceCallRector` fixer rule #60670 #
-
Change Pop managed queue jobs from the cloud-agent instead of SQS #60660 also 12.x #
-
Change Pop managed queue jobs from the cloud-agent instead of SQS #60659 #
-
Performance Bulk SQS dispatch uses SendMessageBatch #60645 #
Upstream: “Send bulk SQS jobs via SendMessageBatch”
Bus::batch()and any other bulk push against SQS sent one API call per job. They now go ten at a time throughSendMessageBatch— an order of magnitude fewer round trips, and an order of magnitude fewer requests on the bill, for a thousand-job batch. -
Feature Support inspecting reserved jobs on the queue fake #60644 #
Laravel 13.18.1 2 Jul 2026 · 2 changes
Laravel 13.18.0 30 Jun 2026 · 4 changes
-
Fix Fix RateLimited middleware not serializing releaseAfter in __sleep() #60609 #
-
Feature WorkerStopping exposes the jobs-processed count and last job timestamp #60592 #
Upstream: “Expose jobs processed count and last job timestamp on the WorkerStopping event”
-
Change DebounceLock reworked #60575 #
Upstream: “13.x-improve-DebounceLock”
Laravel 13.17.0 23 Jun 2026 · 2 changes
Laravel 13.16.0 16 Jun 2026 · 4 changes
Laravel 13.15.0 9 Jun 2026 · 14 changes
-
Internals Add generic to QueueRoutes::all() return type #60447 #
-
Feature Add missing type hints to WorkerIdle and listenForSignals #60389 #
-
Fix Fix FIFO queue name normalization in Cloud managed queues #60316 #
-
Fix Fix queue:failed command to show real class name #60279 also 12.x #
-
Change Throw ManagedQueueNotFoundException when a managed queue is missing #60276 also 12.x #
-
Change wip 4f91e08bd4f4 #
-
Change remove comment 4614124208e5 #
-
Change stop-when-empty-for f6a129544165 #
Laravel 13.14.0 4 Jun 2026 · 2 changes
Laravel 13.13.0 2 Jun 2026 · 5 changes
Laravel 13.12.0 26 May 2026 · 6 changes
Laravel 13.11.0 19 May 2026 · 1 change
Laravel 13.10.0 19 May 2026 · 10 changes
-
Feature Add JSON output option to ListFailedCommand and corresponding tests #60168 #
-
Change Optionally flush the SQS overflow store on queue:clear #60138 #
-
Change Pass WorkerOptions to Pausing/Resuming/Interrupted #60135 #
-
Feature WorkerIdle: the event for a worker with nothing to do #60134 #
Upstream: “Add WorkerIdle event”
Fires when a poll finds every queue empty. The obvious use is scaling — an idle worker is a worker you are paying for — but it is also the cleanest signal that a queue has drained, which is what a deploy gate or a test harness usually wants and previously had to infer from a size of zero it might have read mid-pop.
Laravel 13.9.0 13 May 2026 · 13 changes
-
Change Replace @return with @var on docblocks for properties #60087 #
-
Change Display error in `queue:pause` when `Worker` isn't pausable #60023 #
-
Change Ensure that the named arguments are sorted during a call #60017 #
-
Fix Fix issue using custom aws credential providers #60000 #
-
Feature SQS payloads larger than 256KB can spill to disk #59734 #
Upstream: “Add optional disk storage for large SQS queue payloads”
SQS rejects a message over 256KB, and the failure arrives at dispatch as an SDK exception rather than as anything queue-shaped. An oversized payload can now be written to a filesystem disk with a pointer put on the queue instead. 13.10 added flushing that overflow store on
queue:clear, so clearing a queue no longer strands the payloads it pointed at.It remains a rescue rather than a licence: a job whose arguments run to a quarter of a megabyte is usually a job that should be passed an ID.
Laravel 13.8.0 5 May 2026 · 6 changes
-
Feature Reading the jobs inside a queue, from the framework #59997 #
Upstream: “Add all* queue inspection methods”
pendingJobs(),delayedJobs()andreservedJobs()return the payloads themselves as collections, withallPendingJobs()and friends spanning every queue on the connection. Before this, seeing what was actually queued meantLRANGEagainst Redis and unserializing the payloads by hand.It is the foundation for the counting methods that followed:
allXSize()in 13.27 andtotalSize()in 13.31, which answer "how much is queued across everything" without materialising the payloads. -
Feature Add enum support to QueueFake assertPushedOn method #59990 #
-
Feature Worker pausing and resuming events #59895 #
Upstream: “Add Worker Pausing/Resuming events”
-
Change Memoize credentials in SqsConnector #59867 also 12.x #
-
Feature Support named credential providers for SQS queue connections #59754 #
-
Change normalize to string 5cbe7eb2313a #
Laravel 13.7.0 28 Apr 2026 · 9 changes
-
Fix Correct Batch fresh and add @return to self|null #59891 #
-
Feature Add enum support to setDefaultDriver in QueueManager, LogManager, and SessionManager #59861 #
-
Feature WorkerInterrupted #59848 #
Upstream: “Introduce WorkerInterrupted event”
-
Change Update constructor parameter type for limiterName #59841 #
-
Feature Jobs can react to the signals sent to their worker #59833 #
Upstream: “Allow jobs to react to worker signals”
A long-running job could not previously tell that its worker had been asked to stop; it ran to completion or was killed at the supervisor's timeout, mid-flight, with whatever it had already committed left behind. A job can now observe the signal and wind itself down — checkpoint, stop at a batch boundary, release itself cleanly — which is the difference between a deploy costing you a retry and a deploy costing you a half-written import.
-
Fix Fix PendingDispatch resolving Cache for every dispatched job #59821 #
-
Change Allowing `DebounceFor` attribute to be inherited #59795 #
Laravel 13.6.0 21 Apr 2026 · 7 changes
-
Feature Support named credential providers for SQS queue connections #59733 #
-
Change Change attempts column type from tiny to small integer #59718 #
-
Feature Debounceable jobs: #[DebounceFor] #59507 #
Upstream: “Add debounceable queued jobs”
A debounced job waits for a quiet period before it runs, and each new dispatch during that period pushes the deadline back — so a burst of fifty "rebuild this report" dispatches produces one run, after the burst, rather than one run per dispatch or one run at the start.
It is the third member of a family that is easy to confuse.
ShouldBeUniquedrops a duplicate dispatch outright.ShouldBeUniqueUntilProcessinglets one new copy be queued while the first runs.#[DebounceFor]keeps collapsing dispatches until the input stops arriving, which is what you actually want for "recompute when this changes". 13.18 reworked the lock behind it, 13.26 extended it to queued listeners, and 13.27 fixed the max-wait window not restarting after a run.
Laravel 13.5.0 14 Apr 2026 · 6 changes
-
Feature Support #[Delay] attribute on queued mailables #59580 #
-
Fix Fix ShouldBeUniqueUntilProcessing retries releasing locks they don't own #59567 #
-
Change formatting 64a0ff275c68 #
-
Fix fix: fix 8563ff12bc9e #
-
Change feat: redis cluster full support d6be8af77ce3 #
Laravel 13.4.0 7 Apr 2026 · 5 changes
Laravel 13.3.0 1 Apr 2026 · 9 changes
-
Performance prefer `isset()` over `in_array()` for better performance #59457 #
-
Change Cache getLockForPopping() result in DatabaseQueue #59435 #
-
Feature Add enum support to QueueManager connection methods #59389 #
-
Fix Fix dependency injection of faked queueing dispatcher #59378 #
-
Change Update shared lock key documentation for clarity #59375 #
-
Change formatting 628e7cb77ad2 #
Laravel 13.2.0 24 Mar 2026 · 5 changes
Laravel 13.1.1 18 Mar 2026 · 1 change
Laravel 13.1.0 18 Mar 2026 · 2 changes
Laravel 13.0.0 17 Mar 2026 · 20 changes
-
Feature BatchCanceled carries the exception that cancelled the batch #59163 #
Upstream: “Add Exception to BatchCanceled event”
-
Internals Add missing @throws into docblock for various methods #59016 #
-
Change Refactor parameter names that are implemented from the interface for consistency #59015 #
-
Breaking JobAttempted carries the exception, not a boolean #58954 #
Upstream: “Ensure SyncQueue JobAttempted gets the actual exception”
JobAttempted::$exceptionOccurred, a boolean, is replaced by$exception, which holds theThrowableornull. A listener checking the old property silently reads nothing.This is the event to count processed jobs from — it is the only one that distinguishes a job that was released by
RateLimitedorWithoutOverlappingfrom one that actually finished, viaisReleased()andsuccessful(). Now it can also say why without a second listener onJobFailed. -
Breaking The Queue contract gained four size methods #58914 #
Upstream: “add missing methods to Queue interface”
Illuminate\Contracts\Queue\Queuenow declarespendingSize(),delayedSize(),reservedSize()andcreationTimeOfOldestPendingJob()alongside thesize()it always had. Every first-party driver implements them; any custom queue driver you maintain does not, and will fatal on instantiation until you add them.The last of the four is the interesting one. Queue length cannot tell ten thousand fast jobs from one job nobody has picked up in fifteen minutes; the creation time of the oldest pending job can, and it is now something the framework will answer for any connection rather than something you compute by reading Redis yourself.
-
Change Queued notifications honour #[DeleteWhenMissingModels] #58908 #
Upstream: “feat: respect DeleteWhenMissingModels attribute on queued notifications”
-
Feature Job configuration as PHP attributes #58578 #
Upstream: “Attributes”
The settings that were properties and methods on a job class became attributes. Ten of them arrived with 13.0 —
#[Tries],#[Backoff],#[Timeout],#[FailOnTimeout],#[MaxExceptions],#[Queue],#[Connection],#[UniqueFor],#[WithoutRelations]and#[DeleteWhenMissingModels]— with#[DebounceFor]and#[Delay]following during the 13.x line, for twelve in 13.33.Worth knowing for anything that reads job configuration from outside the worker: these are read by
Illuminate\Queue\Attributes\ReadsQueueAttributes, so a tool that only looks for a$triesproperty now sees nothing on a job that sets#[Tries(5)]. -
Feature add dispatchAfterResponse to the Dispatcher Contract #58428 #
-
Feature WorkerStopping says why the worker stopped #58341 #
Upstream: “Add reason to WorkerStopping event”
The event gained a reason, so "the worker exited" stops being a single undifferentiated signal: a graceful
queue:restart, a memory limit, a--max-jobsceiling and a timeout are now told apart from a listener. 13.18 added the jobs-processed count and the timestamp of the last job, 13.30.1 and 13.31 added the connection and queue, and 13.30 started printing the reason inqueue:work's own output. -
Feature Queue routing: default a job class to a queue in one place #58094 #
Upstream: “Add ability to default queue by class type”
Queue::route(ProcessPodcast::class, 'podcasts', 'redis')declares, centrally, which connection and queue a job class belongs on. Until now that decision lived on the job — an$queueproperty or anonQueue()call at every dispatch site — which meant moving one job between queues during an incident was a code change.Note the real signature is
route($class, $queue = null, $connection = null); the release notes show it with named arguments, and positionally the queue comes before the connection. It accepts an array of classes, and since 13.15 the queue may be a backed enum. -
Breaking QueueBusy::$connection renamed to $connectionName #56673 #
Upstream: “Make QueueBusy event consistent with other queue events”
Every other queue event calls it
$connectionName;QueueBusycalled it$connection. It now matches. If you listen forQueueBusy— the event behindqueue:monitor, which is how most people alert on backlog — the listener reads a property that no longer exists and will throw. -
Feature Listeners can decide deleteWhenMissingModels at runtime #55508 #
Upstream: “Allow Listeners to dynamically specify deleteWhenMissingModels”
-
Feature Add #[\Override] to the BatchFake class methods #55358 #
Common questions
What changed about queues in Laravel 13?
The headline addition is queue routing — Queue::route() declares which connection and queue a job class defaults to, centrally, instead of at every dispatch site. Alongside it: job configuration as PHP attributes (#[Tries], #[Backoff], #[Timeout], #[FailOnTimeout] and nine more), a full queue inspection API on the Queue contract, and debounceable jobs. Over the 13.x patch releases the largest single theme has been pausing: a global pause switch, pauses with a TTL, and worker output that distinguishes a paused queue from an empty one.
What breaks in the queue when upgrading from Laravel 12 to 13?
Three things, all low impact unless you maintain a custom queue driver. The Illuminate\Contracts\Queue\Queue contract gained pendingSize(), delayedSize(), reservedSize() and creationTimeOfOldestPendingJob(), which every custom driver must now implement. JobAttempted replaced the boolean $exceptionOccurred with $exception, holding the Throwable or null. And QueueBusy::$connection was renamed $connectionName to match every other queue event, which breaks listeners behind queue:monitor alerting.
How do I pause all Laravel queues at once?
php artisan queue:pause --all, added in Laravel 13.25, or Queue::pauseAll() in code, with Queue::resumeAll() to undo it. Paused workers keep running and keep polling but pop nothing, so the backlog builds in Redis rather than being lost with a killed process, and jobs already running are unaffected. Queue::pauseFor() sets a TTL so the pause expires on its own rather than outliving the incident.
What is #[DebounceFor] and how is it different from ShouldBeUnique?
A debounced job waits for a quiet period before running, and every dispatch during that period pushes the deadline back — so a burst of fifty dispatches produces one run, after the burst. ShouldBeUnique instead drops a duplicate dispatch outright and holds its lock until the job completes; ShouldBeUniqueUntilProcessing releases at the moment a worker starts it. Debouncing is what you want for "recompute when this changes"; uniqueness is what you want for "never queue two of these".
Can I see what is inside a Laravel queue without reading Redis?
Since Laravel 13.8, yes. pendingJobs(), delayedJobs() and reservedJobs() return the payloads as collections, allPendingJobs() and friends span every queue on the connection, and the counting methods — pendingSize(), delayedSize(), reservedSize(), totalSize() — answer the same questions without materialising anything. Before that, inspecting a backlog meant LRANGE against Redis and unserializing payloads by hand.
Why does this changelog list changes the Laravel release notes do not call queue changes?
Because it is assembled from changed file paths rather than from wording. Every commit in every 13.x release is kept if it touched a file that decides how background work behaves — Illuminate\Queue, Illuminate\Bus, the queue contracts, the queue console commands — plus queue-aware files elsewhere, such as the queued mailable that was pushed to the wrong queue when its name was a backed enum. A subject line that never says "queue" is caught anyway.