News

Pause All Queues and a New artisan dev UI in Laravel 13.25

Published
Pause All Queues and a New artisan dev UI in Laravel 13.25 image

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 --all and queue:resume --all, plus Queue::pauseAll() and Queue::resumeAll()
  • artisan dev runs through @laravel/multiplex with tabs, stream, and inline modes
  • Image implements Responsable, gains Image::fromStream(), and toFormat() is now public
  • A UniqueJobSkipped event, the timeout value on JobTimedOut, and fail-on-timeout for notifications
  • withoutCookies() on responses and a foreignUlidFor() schema helper
  • Request::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 --all
php 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 --stream
php 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 like force_ip_resolve => v4 breaks the socket connection. A new Factory::withoutGlobalConfiguration() closure isolates agent traffic from application-level client configuration (#61064, #61068)
  • Queued broadcast events lost the true default for $deleteWhenMissingModels, so a model deleted before the worker picked up the job failed with a ModelNotFoundException instead of being discarded (#61074)
  • Gate::forUser() copied abilities, policies, and callbacks but not the configured default denial response, so a gate set up with Response::denyAsNotFound() fell back to the framework default (#61087)
  • Str::substrReplace() threw a TypeError on array arguments after the multibyte rewrite passed them straight to mb_substr(). Array calls now delegate to PHP's native substr_replace() (#61105)
  • Backed enum queue names are respected when queueing mailables (#61066), queue drivers match the fake for enum queue names (#61116), MailFake preserves queue resolution on queued mailables (#61114), and bulk pushes to DatabaseQueue respect 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::call left 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 Error instead of the intended InvalidArgumentException (#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:list converts 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), and ColumnDefinition::unsigned() (#61123)
  • Support for brick/math ^0.19 (#61133)

References

Paul Redmond photo

Staff writer at Laravel News. Full stack web developer and author.

Sponsored

acquaintsoft logo
Acquaint Softtech

Hire Laravel developers with AI expertise at $20/hr. Get started in 48 hours.

Visit Acquaint Softtech

The latest

View all →
Laravel Terminal UI for the artisan dev Command image

Laravel Terminal UI for the artisan dev Command

Read article
Laravel monitoring that doesn't bill you by your traffic image

Laravel monitoring that doesn't bill you by your traffic

Read article
Mock PHP Classes in Tests With the Double Library image

Mock PHP Classes in Tests With the Double Library

Read article
Laravel Discount: Coupon Codes, Usage Limits, and Stacking image

Laravel Discount: Coupon Codes, Usage Limits, and Stacking

Read article
Laravel Truss: Live Interactive Database ER Diagrams image

Laravel Truss: Live Interactive Database ER Diagrams

Read article
Laravel artisan dev: Run Server, Queue, Logs, and Vite image

Laravel artisan dev: Run Server, Queue, Logs, and Vite

Read article