What's New in Laravel 13.31
The Laravel team released v13.31.0 on September 8, 2026, packed with quality-of-life improvements for queue management, Eloquent relationships, testing, and front-end tooling. Here is a breakdown of the highlights.
Queue totalSize() Method
Contributed by Jack Bayliss, the new Queue::totalSize() method gives you a single call to count every job on a queue connection — pending, delayed, and reserved — without manually summing three separate methods.
// Before
$total = Queue::totalPendingSize()
+ Queue::totalDelayedSize()
+ Queue::totalReservedSize();
// After
$total = Queue::totalSize();
This is especially useful for dashboards and monitoring scripts that need a quick health check on queue depth.
JobInterrupted Event
Also contributed by Jack Bayliss, the new JobInterrupted event fires whenever a job that implements the Interruptible contract is interrupted by the queue worker. You can listen for it to run cleanup logic, flush state, or write an audit log entry.
use Illuminate\Queue\Events\JobInterrupted;
Event::listen(JobInterrupted::class, function (JobInterrupted $event) {
// Perform cleanup or logging here
});
This closes a long-standing gap: previously there was no first-class hook for reacting to a worker-initiated interruption.
Chaperone Support for BelongsToMany Pivot Models
Caleb White added chaperone support to BelongsToMany relationships. When you use a custom pivot model that defines BelongsTo relations back to the parent and related models, calling ->chaperone() on the relationship now hydrates those relations from already-loaded model instances instead of issuing extra database queries.
class Tag extends Model
{
public function posts(): BelongsToMany
{
return $this->belongsToMany(Post::class)
->using(PostTagPivot::class)
->chaperone(); // auto-guesses relationship names
}
}
If the relationship names are non-standard, you can override them explicitly:
->chaperone(declaring: 'tag', related: 'post');
If no custom pivot model is provided, ->chaperone() is a no-op, so it is safe to add without side effects.
once Assertions for Mail and Notification Fakes
@talaridisTh added expressive assertSentOnce / assertQueuedOnce helpers that replace the verbose *Times(..., 1) pattern:
// Before
Mail::assertSentTimes(OrderShipped::class, 1);
Notification::assertSentToTimes($user, InvoicePaid::class, 1);
// After
Mail::assertSentOnce(OrderShipped::class);
Notification::assertSentToOnce($user, InvoicePaid::class);
The same pattern applies to assertQueuedOnce and assertSentOnDemandOnce.
Vite devServerUrl() Helper
@ramonmalcolm10 added a devServerUrl() method to the Vite class, making it straightforward to retrieve the running Vite development server URL programmatically — useful when generating asset URLs outside of standard Blade directives.
Additional Fixes and Improvements
- Redis queue driver is now cluster-safe (
bulk()uses node-lessMULTI;allQueueNames()usesKEYS). lazy()andlazyById()no longer ignorelimit()andoffset().assertJsonMissingPath()now correctly handles wildcards.- Memory leak fix in the HTTP client.
UsePolicyandUseEloquentBuilderattributes now resolve from parent classes.RateLimitedjob middleware no longer hits limits that should not block the job.- Context is now correctly propagated to concurrent processes.
Key Takeaways
Queue::totalSize()replaces three separate calls with one.JobInterruptedevent enables first-class cleanup hooks for interruptible jobs.BelongsToManychaperone eliminates redundant queries when using custom pivot models withBelongsTorelations.assertSentOnce/assertQueuedOncemake test assertions more readable.Vite::devServerUrl()exposes the dev server URL as a first-class API.
View the full Laravel 13.31.0 release notes and diff on Laravel News →