Laravel Custom Pipelines Beyond Middleware | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Laravel Pipeline Pattern: Building Custom Pipelines Beyond Middleware

 Laravel Pipeline Pattern: Building Custom Pipelines Beyond Middleware
======================================================================

 The Pipeline pattern in Laravel is far more powerful than middleware alone. Learn how to build typed, composable pipelines for domain workflows, data transformation, and multi-stage processing with full testability.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 26 Jun 2026 · Updated 26 Jun 2026 · 4 min read

ShareCopy linkCopied

 ![Laravel Pipeline Pattern: Building Custom Pipelines Beyond Middleware](https://cdn.msaied.com/296/afbac95c7f4aac1cee83eb2c87541369.png) 

  On this page +1. [The Pipeline Pattern in Laravel: Beyond HTTP Middleware](#the-pipeline-pattern-in-laravel-beyond-http-middleware)
2. [The Core API](#the-core-api)
3. [Typed Payloads: Stop Passing Arrays](#typed-payloads-stop-passing-arrays)
4. [Writing a Pipe](#writing-a-pipe)
5. [Short-Circuiting When You Need It](#short-circuiting-when-you-need-it)
6. [Registering Pipelines as Named Services](#registering-pipelines-as-named-services)
7. [Testing Pipelines with Pest](#testing-pipelines-with-pest)
8. [Key Takeaways](#key-takeaways)

 The Pipeline Pattern in Laravel: Beyond HTTP Middleware
-------------------------------------------------------

Most Laravel developers know `Pipeline` only as the engine behind HTTP middleware. But `Illuminate\Pipeline\Pipeline` is a general-purpose, composable tool you can use anywhere in your domain — order processing, import validation, document transformation, multi-stage approval flows.

This article shows how to design typed, testable pipelines that carry real domain meaning.

---

### The Core API

```php
use Illuminate\Pipeline\Pipeline;

$result = app(Pipeline::class)
    ->send($payload)
    ->through([
        StageOne::class,
        StageTwo::class,
    ])
    ->thenReturn();

```

`thenReturn()` returns the final `$payload` after all pipes run. Each pipe receives `($payload, Closure $next)` and must call `$next($payload)` to continue — identical to middleware.

---

### Typed Payloads: Stop Passing Arrays

The single biggest improvement you can make is replacing anonymous arrays with a typed DTO.

```php
final class OrderImportContext
{
    public function __construct(
        public readonly array  $rawRow,
        public ?Product        $product   = null,
        public ?Customer       $customer  = null,
        public array           $errors    = [],
    ) {}

    public function withError(string $message): self
    {
        $clone = clone $this;
        $clone->errors[] = $message;
        return $clone;
    }
}

```

Each pipe receives a fully typed object. IDE completion works. Pipes are self-documenting.

---

### Writing a Pipe

A pipe is any class with a `handle` method (or `__invoke` if you prefer).

```php
final class ResolveProduct
{
    public function handle(OrderImportContext $ctx, Closure $next): OrderImportContext
    {
        $product = Product::query()
            ->where('sku', $ctx->rawRow['sku'] ?? '')
            ->first();

        if ($product === null) {
            return $next($ctx->withError('Unknown SKU: ' . ($ctx->rawRow['sku'] ?? 'n/a')));
        }

        $ctx = clone $ctx;
        $ctx->product = $product;

        return $next($ctx);
    }
}

```

Notice: even on error we call `$next()`. This lets downstream pipes accumulate *all* errors in one pass rather than short-circuiting on the first failure.

---

### Short-Circuiting When You Need It

Sometimes you *do* want to stop early — for example, a hard authentication failure.

```php
final class AssertActiveCustomer
{
    public function handle(OrderImportContext $ctx, Closure $next): OrderImportContext
    {
        if ($ctx->customer?->isSuspended()) {
            // Return without calling $next — pipeline stops here.
            return $ctx->withError('Customer account is suspended.');
        }

        return $next($ctx);
    }
}

```

The pipeline returns whatever value the pipe returns, so the caller always gets an `OrderImportContext`.

---

### Registering Pipelines as Named Services

Bind your pipeline configuration in a service provider so it's injectable and swappable:

```php
$this->app->bind(OrderImportPipeline::class, function ($app) {
    return new class($app) {
        public function __construct(private \Illuminate\Contracts\Container\Container $app) {}

        public function run(OrderImportContext $ctx): OrderImportContext
        {
            return (new Pipeline($this->app))
                ->send($ctx)
                ->through([
                    ResolveProduct::class,
                    ResolveCustomer::class,
                    AssertActiveCustomer::class,
                    ValidateQuantity::class,
                    PersistOrder::class,
                ])
                ->thenReturn();
        }
    };
});

```

Inject `OrderImportPipeline` wherever you need it. Swap the pipe list in tests.

---

### Testing Pipelines with Pest

```php
it('accumulates errors for unknown sku and suspended customer', function () {
    $ctx = new OrderImportContext(
        rawRow: ['sku' => 'GHOST-99', 'qty' => 1],
        customer: Customer::factory()->suspended()->make(),
    );

    $result = app(OrderImportPipeline::class)->run($ctx);

    expect($result->errors)
        ->toContain('Unknown SKU: GHOST-99')
        ->toContain('Customer account is suspended.');
});

```

Because each pipe is a plain class, you can also unit-test pipes in isolation:

```php
it('short-circuits on suspended customer', function () {
    $ctx = new OrderImportContext(
        rawRow: [],
        customer: Customer::factory()->suspended()->make(),
    );

    $pipe   = new AssertActiveCustomer();
    $result = $pipe->handle($ctx, fn ($c) => $c);

    expect($result->errors)->toContain('Customer account is suspended.');
});

```

---

### Key Takeaways

- **Use typed DTOs** as the pipeline payload — never raw arrays.
- **Accumulate vs. short-circuit** is a deliberate design choice per pipeline.
- **Bind named pipelines** in service providers for DI and testability.
- **Each pipe is a plain class** — unit-testable without bootstrapping the full pipeline.
- The `Pipeline` facade is available globally, but prefer `app(Pipeline::class)` for container-resolved pipes.
- Pipelines compose naturally with Laravel's queue system: serialize the DTO, dispatch a job, run the pipeline inside the job.

- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [pipeline](https://www.msaied.com/public/articles?search=pipeline)
- [clean-architecture](https://www.msaied.com/public/articles?search=clean-architecture)
- [design-patterns](https://www.msaied.com/public/articles?search=design-patterns)

 Frequently asked questions 
---------------------------

  Can I use closures instead of classes as pipes?Yes. The Pipeline accepts any callable, including closures. However, named classes are strongly preferred in production code because they are individually testable, injectable via the container, and visible in stack traces.

   How does the Laravel Pipeline differ from a simple foreach loop over handlers?The Pipeline uses a nested closure chain (similar to a Russian-doll middleware stack), which means each pipe controls whether and when to call the next stage. A foreach loop cannot short-circuit or wrap the downstream execution — for example, a pipe cannot wrap $next() in a database transaction and roll back on failure.

   Is there a performance cost to using Pipeline for high-throughput code paths?The overhead is negligible for typical domain workflows. The closure chain adds a small amount of stack depth, but this is rarely measurable against real I/O. For extremely tight loops (e.g., processing millions of rows in memory), a direct method call chain will be marginally faster, but the Pipeline is appropriate for the vast majority of Laravel use cases.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://www.msaied.com/public#person)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](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleRead/Write Splitting, Connection Pooling, and Sticky Reads in Laravel](https://www.msaied.com/public/articles/readwrite-splitting-connection-pooling-and-sticky-reads-in-laravel-2) [Next articleFilament v4 Schema-Based Forms, Infolists, and the Unified Schema API](https://www.msaied.com/public/articles/filament-v4-schema-based-forms-infolists-and-the-unified-schema-api-1)  

   On this page
-------------

1. [The Pipeline Pattern in Laravel: Beyond HTTP Middleware](#the-pipeline-pattern-in-laravel-beyond-http-middleware)
2. [The Core API](#the-core-api)
3. [Typed Payloads: Stop Passing Arrays](#typed-payloads-stop-passing-arrays)
4. [Writing a Pipe](#writing-a-pipe)
5. [Short-Circuiting When You Need It](#short-circuiting-when-you-need-it)
6. [Registering Pipelines as Named Services](#registering-pipelines-as-named-services)
7. [Testing Pipelines with Pest](#testing-pipelines-with-pest)
8. [Key Takeaways](#key-takeaways)

 ###  Have a technical challenge?

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

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  Have a technical challenge?
----------------------------

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

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
