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

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

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

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Job Batching, Chaining, and Rate-Limited Middleware in Laravel Queues        On this page       1. [  Why Basic dispatch() Is Not Enough ](#why-basic-codedispatchcode-is-not-enough)
2. [  Job Batching with Bus::batch() ](#job-batching-with-codebusbatchcode)
3. [  Adding Jobs to a Running Batch ](#adding-jobs-to-a-running-batch)
4. [  Job Chaining with Bus::chain() ](#job-chaining-with-codebuschaincode)
5. [  Rate-Limited Job Middleware ](#rate-limited-job-middleware)
6. [  Custom Backoff on Rate Limit ](#custom-backoff-on-rate-limit)
7. [  Combining All Three ](#combining-all-three)
8. [  Key Takeaways ](#key-takeaways)

  ![Job Batching, Chaining, and Rate-Limited Middleware in Laravel Queues](https://cdn.msaied.com/515/f0ef4270f8cb79ceff107c7a7c63f1ed.png)

  #laravel   #queues   #jobs   #horizon   #async  

 Job Batching, Chaining, and Rate-Limited Middleware in Laravel Queues 
=======================================================================

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

       Table of contents

1. [  01   Why Basic dispatch() Is Not Enough  ](#why-basic-codedispatchcode-is-not-enough)
2. [  02   Job Batching with Bus::batch()  ](#job-batching-with-codebusbatchcode)
3. [  03   Adding Jobs to a Running Batch  ](#adding-jobs-to-a-running-batch)
4. [  04   Job Chaining with Bus::chain()  ](#job-chaining-with-codebuschaincode)
5. [  05   Rate-Limited Job Middleware  ](#rate-limited-job-middleware)
6. [  06   Custom Backoff on Rate Limit  ](#custom-backoff-on-rate-limit)
7. [  07   Combining All Three  ](#combining-all-three)
8. [  08   Key Takeaways  ](#key-takeaways)

 Why Basic `dispatch()` Is Not Enough
------------------------------------

Single-job dispatching works fine for isolated tasks, but real SaaS workloads demand coordination: import a CSV, notify each row's owner, then send a summary email. Get any step wrong and you want partial retries — not a full restart. Laravel's batch and chain APIs, combined with rate-limited job middleware, give you that control.

---

Job Batching with `Bus::batch()`
--------------------------------

Batches let you dispatch a collection of jobs and react when the whole set finishes, partially fails, or is cancelled.

```php
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;

$batch = Bus::batch([
    new ProcessRowJob($row) for $row in $rows, // spread or array
])
->then(fn (Batch $batch) => SummaryMail::dispatch($batch->id))
->catch(fn (Batch $batch, Throwable $e) => Log::error('Batch failed', [
    'batch' => $batch->id,
    'error' => $e->getMessage(),
]))
->finally(fn (Batch $batch) => BatchCompleted::dispatch($batch->id))
->name('csv-import')
->allowFailures()   // keep running even if some jobs fail
->dispatch();

```

> `allowFailures()` is critical for large imports: one bad row should not cancel 10,000 others.

Track progress in Filament or a dashboard via `$batch->progress()`, `$batch->failedJobs`, and `$batch->pendingJobs`.

### Adding Jobs to a Running Batch

Inside a batched job you can append more work — useful for tree-shaped workloads:

```php
public function handle(): void
{
    $this->batch()->add([
        new ProcessChildJob($this->parentId, $child)
        foreach ($this->children() as $child),
    ]);
}

```

---

Job Chaining with `Bus::chain()`
--------------------------------

Chains enforce strict sequential execution. If any job fails, the rest are abandoned.

```php
Bus::chain([
    new VerifyPayment($orderId),
    new FulfillOrder($orderId),
    new SendConfirmationEmail($orderId),
])
->catch(fn (Throwable $e) => Order::fail($orderId, $e->getMessage()))
->dispatch();

```

You can mix batches inside chains for fan-out/fan-in patterns:

```php
Bus::chain([
    new PrepareImport($fileId),
    Bus::batch($rowJobs)->allowFailures(),
    new FinaliseImport($fileId),
])->dispatch();

```

This runs `PrepareImport`, then all row jobs in parallel, then `FinaliseImport` — a powerful pattern for ETL pipelines.

---

Rate-Limited Job Middleware
---------------------------

Throttling at the job level prevents hammering third-party APIs regardless of how many workers you run.

```php
use Illuminate\Queue\Middleware\RateLimited;
use Illuminate\Support\Facades\RateLimiter;

// AppServiceProvider::boot()
RateLimiter::for('stripe', fn () =>
    Limit::perMinute(100)->by('stripe-api')
);

// Inside the job
public function middleware(): array
{
    return [new RateLimited('stripe')];
}

```

When the limit is hit, the job is **automatically released back** to the queue with an exponential backoff — no manual `$this->release()` needed.

### Custom Backoff on Rate Limit

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

public function middleware(): array
{
    return [(new RateLimitedWithRedis('stripe'))->dontRelease()];
    // dontRelease() deletes the job instead of re-queuing — use carefully
}

```

`RateLimitedWithRedis` uses atomic Lua scripts for precise per-second limits, making it safer under Horizon's multi-worker concurrency.

---

Combining All Three
-------------------

A production import pipeline might look like:

```php
Bus::chain([
    new ValidateFile($fileId),                        // sequential
    Bus::batch($parseJobs)->allowFailures(),           // parallel parse
    Bus::batch($enrichJobs)->allowFailures(),          // parallel API calls (rate-limited)
    new GenerateReport($fileId),                      // sequential
])->dispatch();

```

Each `enrichJob` carries `RateLimited('external-api')` middleware, so the batch fans out as fast as the limiter allows without a single line of throttle logic in the business code.

---

Key Takeaways
-------------

- Use **`Bus::batch()`** for parallel fan-out; use **`allowFailures()`** for fault-tolerant imports.
- Use **`Bus::chain()`** for strict sequential steps; nest batches inside chains for fan-out/fan-in.
- Attach **`RateLimited`** middleware at the job level — it survives worker restarts and scales across all Horizon processes.
- Prefer **`RateLimitedWithRedis`** over the plain variant when you need sub-second precision.
- Track batch state via `$batch->progress()` for real-time dashboards without polling your database directly.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fjob-batching-chaining-and-rate-limited-middleware-in-laravel-queues-4&text=Job+Batching%2C+Chaining%2C+and+Rate-Limited+Middleware+in+Laravel+Queues) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fjob-batching-chaining-and-rate-limited-middleware-in-laravel-queues-4) 

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

  3 questions  

     Q01  What happens to a batch when one job throws an exception and `allowFailures()` is set?        The failed job is recorded in `job_batches.failed_jobs` and the `catch` callback fires, but the remaining pending jobs continue processing. The batch only moves to `finally` once all jobs have either completed or failed. 

      Q02  Can I use `RateLimited` middleware with batched jobs?        Yes. Each job in a batch is an independent queue message, so middleware is applied per-job. Rate-limited jobs are released back to the queue and retried, which may slow overall batch completion but will not cancel the batch. 

      Q03  How do I prevent a chain from silently swallowing failures?        Always attach a `-&gt;catch()` callback to `Bus::chain()`. Without it, a failed job abandons the rest of the chain with no notification. The callback receives the `Throwable` so you can alert, compensate, or update domain state. 

  Continue reading

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

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

 [ ![Extract an Image's Dominant Color in Laravel 13.24](https://cdn.msaied.com/516/850cdb7ffa533b75462e7b29e8b25eb4.png) Laravel Image Processing PHP 

### Extract an Image's Dominant Color in Laravel 13.24

Laravel 13.24 added dominantColor() to the first-party image API. Learn how to extract, store, and use an imag...

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

 5 Aug 2026     3 min read  

  Read    

 ](https://msaied.com/articles/extract-an-images-dominant-color-in-laravel-1324) [ ![Laravel Boost Project Rules: Teach AI Agents Your Team's Conventions](https://cdn.msaied.com/517/850c73bd5504aa6154c76e452ad136c1.png) Laravel Boost AI Agents Laravel 

### Laravel Boost Project Rules: Teach AI Agents Your Team's Conventions

Laravel Boost v2.5.0 introduces project rules — scoped Markdown files committed to your repo that teach AI age...

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

 5 Aug 2026     4 min read  

  Read    

 ](https://msaied.com/articles/laravel-boost-project-rules-teach-ai-agents-your-teams-conventions) [ ![Image Dominant Color and HEIC Support in Laravel 13.24](https://cdn.msaied.com/514/1db4d7a38103ef4be23345bd39fc7658.png) Laravel 13.24 Image API HEIC 

### Image Dominant Color and HEIC Support in Laravel 13.24

Laravel 13.24 adds dominant color detection, HEIC/AVIF image support, a modelKeys() query builder method, a ne...

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

 4 Aug 2026     4 min read  

  Read    

 ](https://msaied.com/articles/image-dominant-color-and-heic-support-in-laravel-1324) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://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://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://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)
