Skip to content

Queue totalSize() and JobInterrupted Event in Laravel 13.31

Laravel 13.31 ships Queue::totalSize(), a new JobInterrupted event, chaperone support for BelongsToMany pivot models, cleaner test assertions, and a Vite devServerUrl() helper.

Published · Updated · 3 min read

Queue totalSize() and JobInterrupted Event in Laravel 13.31

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-less MULTI; allQueueNames() uses KEYS).
  • lazy() and lazyById() no longer ignore limit() and offset().
  • assertJsonMissingPath() now correctly handles wildcards.
  • Memory leak fix in the HTTP client.
  • UsePolicy and UseEloquentBuilder attributes now resolve from parent classes.
  • RateLimited job 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.
  • JobInterrupted event enables first-class cleanup hooks for interruptible jobs.
  • BelongsToMany chaperone eliminates redundant queries when using custom pivot models with BelongsTo relations.
  • assertSentOnce / assertQueuedOnce make 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 →

Frequently asked questions

What does Queue::totalSize() return in Laravel 13.31?

It returns the combined count of all jobs on a queue connection — pending, delayed, and reserved — in a single method call, replacing the need to manually sum Queue::totalPendingSize(), Queue::totalDelayedSize(), and Queue::totalReservedSize().

When does the JobInterrupted event fire in Laravel?

The JobInterrupted event fires when 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 log the interruption.

What does calling ->chaperone() on a BelongsToMany relationship do?

When a custom pivot model defines BelongsTo relations back to the parent and related models, chaperone() hydrates those relations from already-loaded instances during BelongsToMany hydration, avoiding extra database queries. If no custom pivot model is used, it is a no-op.

Mohamed Said

About the author

Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

About GitHub LinkedIn WhatsApp Email Address My CV

Have a technical challenge?

Tell me what you’re building. I reply within two working days.

Discuss your project