Laravel Job Middleware: Rate Limiting &amp; Skip Patterns | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.msaied.com) [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles  ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

  [ home ](https://www.msaied.com)    [ articles ](https://www.msaied.com/articles)    Laravel Job Middleware: Rate Limiting, Throttling, and Skipping Jobs Without Hacks        On this page       1. [  Laravel Job Middleware: Rate Limiting, Throttling, and Skipping Jobs Without Hacks ](#laravel-job-middleware-rate-limiting-throttling-and-skipping-jobs-without-hacks)
2. [  What Job Middleware Actually Is ](#what-job-middleware-actually-is)
3. [  Built-In Rate Limiting with RateLimited ](#built-in-rate-limiting-with-ratelimited)
4. [  Throttling Bursts with WithoutOverlapping ](#throttling-bursts-with-withoutoverlapping)
5. [  Building a Custom Skip Middleware ](#building-a-custom-skip-middleware)
6. [  Composing Multiple Middleware ](#composing-multiple-middleware)
7. [  Testing Job Middleware in Isolation ](#testing-job-middleware-in-isolation)
8. [  Takeaways ](#takeaways)

  ![Laravel Job Middleware: Rate Limiting, Throttling, and Skipping Jobs Without Hacks](https://cdn.msaied.com/635/e10d72c500d7a25f077552f3098478e8.png)

  #laravel   #queues   #job-middleware   #performance  

 Laravel Job Middleware: Rate Limiting, Throttling, and Skipping Jobs Without Hacks 
====================================================================================

     6 Sep 2026      4 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Laravel Job Middleware: Rate Limiting, Throttling, and Skipping Jobs Without Hacks  ](#laravel-job-middleware-rate-limiting-throttling-and-skipping-jobs-without-hacks)
2. [  02   What Job Middleware Actually Is  ](#what-job-middleware-actually-is)
3. [  03   Built-In Rate Limiting with RateLimited  ](#built-in-rate-limiting-with-ratelimited)
4. [  04   Throttling Bursts with WithoutOverlapping  ](#throttling-bursts-with-withoutoverlapping)
5. [  05   Building a Custom Skip Middleware  ](#building-a-custom-skip-middleware)
6. [  06   Composing Multiple Middleware  ](#composing-multiple-middleware)
7. [  07   Testing Job Middleware in Isolation  ](#testing-job-middleware-in-isolation)
8. [  08   Takeaways  ](#takeaways)

 Laravel Job Middleware: Rate Limiting, Throttling, and Skipping Jobs Without Hacks
----------------------------------------------------------------------------------

Laravel's job middleware feature is one of the most underused tools in the queue ecosystem. Most teams reach for static flags, database locks, or custom queue drivers when they need to control job execution flow. Job middleware gives you a cleaner, composable alternative — and it ships with the framework.

### What Job Middleware Actually Is

A job middleware is a plain PHP class with a single `handle(object $job, Closure $next)` method. You attach it via the `middleware()` method on your job class. Laravel calls each middleware in order before invoking `handle()` on the job itself.

```php
class EnsureUserIsActive
{
    public function handle(object $job, Closure $next): void
    {
        if (! $job->user->is_active) {
            $job->delete();
            return;
        }

        $next($job);
    }
}

```

Attach it to a job:

```php
public function middleware(): array
{
    return [new EnsureUserIsActive];
}

```

No traits, no base classes — just composition.

### Built-In Rate Limiting with RateLimited

Laravel ships `Illuminate\Queue\Middleware\RateLimited`, which integrates with the `RateLimiter` facade. Define a named limiter in `AppServiceProvider` (or a dedicated provider):

```php
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Cache\RateLimiting\Limit;

RateLimiter::for('stripe-webhooks', function (object $job) {
    return Limit::perMinute(30)->by($job->accountId);
});

```

Then apply it:

```php
use Illuminate\Queue\Middleware\RateLimited;

public function middleware(): array
{
    return [new RateLimited('stripe-webhooks')];
}

```

When the limit is exceeded, the job is automatically released back onto the queue with an exponential backoff. You can control the release delay:

```php
new RateLimited('stripe-webhooks')->dontRelease()
// or
new RateLimited('stripe-webhooks')->releaseAfterBackoff($this->attempts())

```

`releaseAfterBackoff` uses the job's attempt count to compute a sensible delay — critical for avoiding thundering-herd re-queues after a rate limit window resets.

### Throttling Bursts with WithoutOverlapping

`WithoutOverlapping` prevents concurrent execution of the same logical job:

```php
use Illuminate\Queue\Middleware\WithoutOverlapping;

public function middleware(): array
{
    return [
        (new WithoutOverlapping($this->userId))
            ->releaseAfter(30)
            ->expireAfter(180),
    ];
}

```

The key insight: `expireAfter` sets a TTL on the atomic lock so a crashed worker doesn't block the job forever. Always set it. The default is no expiry — a silent footgun in production.

### Building a Custom Skip Middleware

Sometimes you need to discard a job based on state that only exists at execution time — not dispatch time. A skip middleware handles this cleanly:

```php
class SkipIfAlreadyProcessed
{
    public function handle(object $job, Closure $next): void
    {
        $key = 'processed:' . $job->idempotencyKey;

        if (cache()->has($key)) {
            $job->delete();
            return;
        }

        $next($job);

        cache()->put($key, true, now()->addHours(24));
    }
}

```

This pattern is particularly useful for webhook processors and import jobs where the same payload can arrive multiple times.

### Composing Multiple Middleware

Middleware compose naturally — order matters:

```php
public function middleware(): array
{
    return [
        new SkipIfAlreadyProcessed,
        new RateLimited('external-api'),
        (new WithoutOverlapping($this->resourceId))->releaseAfter(10),
    ];
}

```

The skip check runs first, avoiding unnecessary lock acquisition and rate-limit counter increments for duplicate jobs.

### Testing Job Middleware in Isolation

Because middleware are plain classes, they're trivially testable with Pest:

```php
it('deletes the job when user is inactive', function () {
    $user = User::factory()->inactive()->make();
    $job = Mockery::mock(ProcessUserReport::class);
    $job->user = $user;
    $job->shouldReceive('delete')->once();

    $next = fn () => null;
    (new EnsureUserIsActive)->handle($job, $next);
});

```

No queue faking, no HTTP calls — pure unit test.

### Takeaways

- Job middleware is the correct abstraction for cross-cutting queue concerns — not base job classes or static state.
- Always set `expireAfter` on `WithoutOverlapping` to prevent permanent lock starvation after worker crashes.
- Use `releaseAfterBackoff` with `RateLimited` to avoid thundering-herd requeues at window reset.
- Skip middleware should call `$job->delete()` explicitly — releasing without deleting re-queues the job.
- Compose middleware in skip-first order to avoid wasting rate-limit budget on jobs that will be discarded anyway.
- Plain classes mean plain unit tests — no queue infrastructure required.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Flaravel-job-middleware-rate-limiting-throttling-and-skipping-jobs-without-hacks&text=Laravel+Job+Middleware%3A+Rate+Limiting%2C+Throttling%2C+and+Skipping+Jobs+Without+Hacks) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Flaravel-job-middleware-rate-limiting-throttling-and-skipping-jobs-without-hacks) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  Does job middleware work with batched and chained jobs?        Yes. Middleware defined in a job's `middleware()` method applies regardless of whether the job is dispatched standalone, inside a batch, or as part of a chain. Each job in the batch or chain runs its own middleware independently. 

      Q02  What happens to a rate-limited job's attempt count when it is released back to the queue?        Releasing a job does not increment its attempt count. Only a failed execution increments `$this-&gt;attempts()`. This means a job can be released many times by rate-limiting middleware without burning through its `$tries` limit. 

      Q03  Can I pass constructor arguments to job middleware dynamically from the job?        Yes. Since middleware are instantiated inside the job's `middleware()` method, you have full access to `$this` and can pass any job property to the middleware constructor — for example, `new WithoutOverlapping($this-&gt;tenantId . ':' . $this-&gt;resourceId)`. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://www.msaied.com/articles) 

 [ ![Filament v3 Infolist Entries: Building Rich Read-Only Detail Pages Without Blade Sprawl](https://cdn.msaied.com/634/13a8abbaba187864d69a5790a448ed46.png) filament laravel infolist 

### Filament v3 Infolist Entries: Building Rich Read-Only Detail Pages Without Blade Sprawl

Filament's Infolist API lets you compose structured, read-only detail views entirely in PHP. Learn how to buil...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 5 Sep 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/filament-v3-infolist-entries-building-rich-read-only-detail-pages-without-blade-sprawl) [ ![Filament v3 Custom Field Plugins: Building a Reusable Signature Pad Component](https://cdn.msaied.com/633/6d6839e7007d1cb38f0421594a4557bf.png) filament laravel livewire 

### Filament v3 Custom Field Plugins: Building a Reusable Signature Pad Component

Learn how to build a production-ready Filament v3 custom field plugin — a signature pad — covering Alpine.js s...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 5 Sep 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/filament-v3-custom-field-plugins-building-a-reusable-signature-pad-component) [ ![Taylor Otwell Disabled GitHub Issues on Most Laravel Open-Source Packages](https://cdn.msaied.com/632/d9612144281f22ce7b18e7ee82a2ea80.png) Laravel Open Source GitHub 

### Taylor Otwell Disabled GitHub Issues on Most Laravel Open-Source Packages

Taylor Otwell has turned off GitHub Issues on most Laravel open-source packages, asking contributors to use a...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 4 Sep 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/taylor-otwell-disabled-github-issues-on-most-laravel-open-source-packages) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [Contact](https://www.msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
