Laravel 13.25 adds a global pause switch that stops every queue on every connection with one command, replaces the process runner behind artisan dev with a tabbed terminal UI, and lets an Image instance be returned directly from a route. The Laravel team released v13.25.0 on August 11, 2026.
queue:pause --allandqueue:resume --all, plusQueue::pauseAll()andQueue::resumeAll()artisan devruns through@laravel/multiplexwith tabs, stream, and inline modesImageimplementsResponsable, gainsImage::fromStream(), andtoFormat()is now public- A
UniqueJobSkippedevent, the timeout value onJobTimedOut, and fail-on-timeout for notifications withoutCookies()on responses and aforeignUlidFor()schema helperRequest::all()now prefers input over files when the two collide
What's New
Pause Every Queue on Every Connection
Laravel has been able to pause an individual queue for a while, but the granularity worked against you during a deployment. An application with a dozen workers spread over several named queues had to pause each connection and queue pair by name, and those names change as features come and go. Reaching for maintenance mode instead takes the whole site down when all you wanted was for workers to stop reserving jobs.
Both console commands now take an --all flag:
php artisan queue:pause --allphp artisan queue:resume --all
The queue argument became optional to make room for it, and the same switch is available on the facade:
use Illuminate\Support\Facades\Queue; Queue::pauseAll();Queue::resumeAll();
The global switch is a single cache key that workers check alongside the per-queue keys, so isPaused() and getPausedQueues() report a queue as paused when either switch is on. The two are independent: resumeAll() clears the global flag and leaves anything you paused individually still paused, which is deliberate so a deploy script cannot accidentally restart a queue somebody parked on purpose.
Two new events, QueuesPaused and QueuesResumed, fire alongside the existing per-queue QueuePaused and QueueResumed. Contributed by @jackbayliss in #61126.
artisan dev Runs Through @laravel/multiplex
The artisan dev command shelled out to concurrently, which interleaves every process into one scrolling feed. A Vite rebuild and a queue worker and Pail all writing at once makes finding the line you care about a scrolling exercise.
It now runs through @laravel/multiplex, a terminal UI with each process in its own tab, search, per-process restart and log clearing, and automatic restart of a process that crashes. Three modes are available, tabs (the default), stream (one interleaved feed you can scroll and search), and inline (plain output, used automatically when there is no TTY). Pick one per run:
php artisan dev --streamphp artisan dev --timestamps --no-restart
Or set the default for the project in a service provider:
use Illuminate\Foundation\DevCommands; DevCommands::stream();DevCommands::withTimestamps();DevCommands::disableAutoRestart();DevCommands::bufferSize(5000);
When the command exits, the buffered logs are printed to the main terminal so nothing is lost on the way out. Windows falls back to concurrently, since multiplex currently supports macOS and Linux only, and the Node floor for the new path is v22.13. The full list of modes, flags, and registration methods is in the artisan dev terminal UI. Contributed by @joetannenbaum in #61100.
Images as HTTP Responses
The first-party image API could transform an image and store it, but handing one back over HTTP meant calling toBytes() and assembling the response yourself. Image now implements Responsable, so a route can return the instance:
use Illuminate\Support\Facades\Image; Route::get('/avatars/{user}', function (User $user) { return Image::fromStorage($user->avatar_path) ->cover(200, 200) ->toWebp() ->quality(80);});
toResponse() returns a 200 with the processed bytes and a Content-Type read from the output, so the header matches whatever format the pipeline produced rather than the source file.
Two smaller additions in the same area. Image::fromStream() builds an instance from a stream resource, reading it lazily and throwing an ImageException if the stream yields nothing:
$image = Image::fromStream(Storage::disk('s3')->readStream($path));
And toFormat() is now public, which replaces the match statement you would otherwise write to turn a user-supplied format string into the right toWebp() or toAvif() call:
return Image::fromUpload($request->file('photo')) ->toFormat($request->string('format')) ->quality(80);
An unsupported format throws an ImageException rather than falling through. All three contributed by @calebdw in #61111, #61109, and #61110.
Queue Observability Additions
A job that is not dispatched because a ShouldBeUnique lock is held disappears without a trace, which makes it hard to tell a working uniqueness constraint from a lock that is never released. The new UniqueJobSkipped event carries the job that was dropped:
use Illuminate\Queue\Events\UniqueJobSkipped; Event::listen(function (UniqueJobSkipped $event) { Log::info('Skipped unique job', ['job' => $event->job::class]);});
It fires from PendingDispatch when the unique lock cannot be acquired, alongside the existing JobDebounced event (#61039).
JobTimedOut gained a third property, $timeout, holding the number of seconds that was exceeded. A worker started with queue:work --timeout=120 applies its own timeout to every job it runs, so without the value on the event there was no way to tell a job's own timeout from the worker's (#61060).
Notifications now honor fail-on-timeout. SendQueuedNotifications reads a $failOnTimeout property or a #[FailOnTimeout] attribute off the notification, which matters when a timeout leaves you unsure whether a third party already delivered the message (#61072):
use Illuminate\Queue\Attributes\FailOnTimeout; #[FailOnTimeout]class OrderShipped extends Notification implements ShouldQueue{ //}
Finally, QueueFake now assigns a uuid to faked jobs, so the queue inspection methods return the same shape they do against a real driver and application code that reads $job->uuid is testable (#60966).
withoutCookies() on Responses
Expiring several cookies meant chaining withoutCookie() once per name. The plural form takes an array:
return response('OK')->withoutCookies(['session', 'tracking', 'preferences']);
It loops over withoutCookie(), so the optional $path and $domain arguments apply to every cookie in the array, and cookie instances work as well as names. Contributed by @xurshudyan in #61115.
foreignUlidFor() Schema Helper
foreignIdFor() already detects the HasUlids trait and produces a ULID column, but there was no explicit helper to match foreignUuidFor(). The trio is now complete:
$table->foreignUlidFor(User::class)->constrained();
The helper infers the column name, the related table, and the referenced key from the model, producing a char(26) column and the matching foreign key constraint. Contributed by @talaridisTh in #61036.
Request::all() Prefers Input Over Files
Request::all() merged the input bag and the file bag with array_replace_recursive(), with files applied last, so a file field and an input field sharing a name resolved to the UploadedFile. The order is now reversed, and input wins:
// POST with input email=taylor@laravel.com and a file also named email$request->all(); // ['email' => 'taylor@laravel.com']$request->email; // 'taylor@laravel.com'$request->file('email') // still the UploadedFile
Nested keys merge the same way, so a profile.avatar file and a profile.name input still both appear, with input taking precedence only where the keys actually collide. file() is unaffected. Contributed by @taylorotwell in #61099.
Other Fixes and Improvements
Http::globalOptions()and global middleware were applied to the framework's own cloud agent unix socket long poll, where an option likeforce_ip_resolve => v4breaks the socket connection. A newFactory::withoutGlobalConfiguration()closure isolates agent traffic from application-level client configuration (#61064, #61068)- Queued broadcast events lost the
truedefault for$deleteWhenMissingModels, so a model deleted before the worker picked up the job failed with aModelNotFoundExceptioninstead of being discarded (#61074) Gate::forUser()copied abilities, policies, and callbacks but not the configured default denial response, so a gate set up withResponse::denyAsNotFound()fell back to the framework default (#61087)Str::substrReplace()threw aTypeErroron array arguments after the multibyte rewrite passed them straight tomb_substr(). Array calls now delegate to PHP's nativesubstr_replace()(#61105)- Backed enum queue names are respected when queueing mailables (#61066), queue drivers match the fake for enum queue names (#61116),
MailFakepreserves queue resolution on queued mailables (#61114), and bulk pushes toDatabaseQueuerespect after-commit dispatch (#60996) - Deactivating cache-backed maintenance mode between the middleware's two cache lookups threw a
TypeError; the middleware now rechecks the state the same way it already did for the file driver (#61121) Container::callleft entries on the build stack when a dependency threw, so later resolutions saw a stale stack (#61041)- Typed cache getters interpolated an enum key into the type mismatch message, producing an
Errorinstead of the intendedInvalidArgumentException(#61056) - HEIC files reported incorrect dimensions (#61010),
LazyCollection::flip()skips values that cannot be array keys (#61081), and#[WithoutTimestamps]is checked when a model decides whether to touch (#61073) - Retry callbacks on asynchronous HTTP requests receive the HTTP method as a third argument, matching synchronous requests, which previously caused an
ArgumentCountError(#61106) - Non-stream resources are rejected in HTTP fake response bodies (#61047), the Cloud log driver sets a socket timeout (#61065, #61082), and signed URL support was adjusted for Vapor (#61129)
schedule:listconverts timezones correctly for range, step, and wildcard cron expressions (#60913),Factory::insert()handles a count of zero (#60911), and a failure while logging a deprecation no longer escalates to a fatal error (#60907)- Type annotation fixes for
Arr::prependKeysWith()(#61034),Str::numbers()(#61053),getMigrationBatches()(#60973),getRememberToken()(#61067), the route binding registrar (#61124), andColumnDefinition::unsigned()(#61123) - Support for
brick/math^0.19 (#61133)
References