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

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

Menu
----

Close 

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

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

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

 The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware
====================================================================

 Laravel's Pipeline class is far more than the engine behind HTTP middleware. Learn how to build typed, composable pipelines for domain workflows, data transformation, and multi-step validation with clean, testable pipe classes.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://msaied.com#person) Published 5 Oct 2026 · Updated 5 Oct 2026 · 3 min read

ShareCopy linkCopied

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

  On this page +1. [The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware](#the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware)
2. [The Anatomy of Illuminate\\Pipeline\\Pipeline](#the-anatomy-of-codeilluminatepipelinepipelinecode)
3. [Building Typed Pipe Classes](#building-typed-pipe-classes)
4. [Encapsulating a Pipeline as a Domain Service](#encapsulating-a-pipeline-as-a-domain-service)
5. [Short-Circuiting and Conditional Pipes](#short-circuiting-and-conditional-pipes)
6. [Testing Individual Pipes](#testing-individual-pipes)
7. [When to Reach for a Pipeline](#when-to-reach-for-a-pipeline)
8. [Takeaways](#takeaways)

 The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware
-------------------------------------------------------------------

Most Laravel developers know `Pipeline` as the engine powering HTTP middleware. Fewer realise it is a first-class, general-purpose class you can pull into any domain layer — no HTTP context required.

The core contract is simple: pass a *payload* through an ordered list of *pipes*, each of which may transform the payload and decide whether to continue. That mental model maps perfectly onto order processing, document enrichment, import validation, and dozens of other domain workflows.

### The Anatomy of `Illuminate\Pipeline\Pipeline`

```php
use Illuminate\Pipeline\Pipeline;

$result = app(Pipeline::class)
    ->send($payload)
    ->through([
        PipeA::class,
        PipeB::class,
    ])
    ->thenReturn(); // returns the final payload

```

Each pipe receives `($payload, Closure $next)` and must call `$next($payload)` to continue — or return early to short-circuit.

### Building Typed Pipe Classes

Rather than closures, prefer invokable classes. They are discoverable, injectable, and individually testable.

```php
final class NormaliseEmailPipe
{
    public function __invoke(OrderData $order, Closure $next): OrderData
    {
        $order = $order->withEmail(
            mb_strtolower(trim($order->email))
        );

        return $next($order);
    }
}

```

Because the container resolves each pipe, you can type-hint dependencies in the constructor:

```php
final class CheckInventoryPipe
{
    public function __construct(
        private readonly InventoryService $inventory,
    ) {}

    public function __invoke(OrderData $order, Closure $next): OrderData
    {
        if (! $this->inventory->isAvailable($order->sku, $order->quantity)) {
            throw new InsufficientStockException($order->sku);
        }

        return $next($order);
    }
}

```

### Encapsulating a Pipeline as a Domain Service

Exposing the raw `Pipeline` call everywhere couples callers to the infrastructure detail. Wrap it:

```php
final class PlaceOrderPipeline
{
    private array $pipes = [
        NormaliseEmailPipe::class,
        CheckInventoryPipe::class,
        ApplyDiscountsPipe::class,
        PersistOrderPipe::class,
    ];

    public function __construct(private readonly Pipeline $pipeline) {}

    public function process(OrderData $order): Order
    {
        return $this->pipeline
            ->send($order)
            ->through($this->pipes)
            ->thenReturn();
    }
}

```

Callers only know about `PlaceOrderPipeline::process()`. Swapping, reordering, or adding pipes is a one-line change inside the service.

### Short-Circuiting and Conditional Pipes

Sometimes a pipe should halt processing without throwing. Return directly instead of calling `$next`:

```php
public function __invoke(ImportRow $row, Closure $next): ImportRow
{
    if ($row->isDuplicate()) {
        return $row->markSkipped(); // does NOT call $next
    }

    return $next($row);
}

```

For conditional inclusion, build the pipe list dynamically:

```php
$pipes = [
    NormaliseEmailPipe::class,
    CheckInventoryPipe::class,
];

if ($order->requiresAgeVerification()) {
    $pipes[] = AgeVerificationPipe::class;
}

```

### Testing Individual Pipes

Because each pipe is an invokable class, unit-testing is trivial:

```php
it('normalises the email to lowercase', function () {
    $pipe = new NormaliseEmailPipe();
    $order = OrderData::fake(['email' => '  USER@EXAMPLE.COM  ']);

    $result = $pipe($order, fn ($o) => $o);

    expect($result->email)->toBe('user@example.com');
});

```

For integration tests, run the full pipeline with a real container and assert on the final state or side-effects.

### When to Reach for a Pipeline

A pipeline is a good fit when:

- Steps are **ordered and composable** but not all always required.
- Each step has a **single responsibility** and should be testable in isolation.
- You want to **inject dependencies** per step without a god-class service.
- The workflow may **grow** — new steps should not require touching existing ones.

Avoid pipelines for simple, linear logic with no variation. A plain service method is clearer there.

### Takeaways

- `Illuminate\Pipeline\Pipeline` is a general-purpose tool, not HTTP-specific.
- Invokable pipe classes get full container resolution — constructor injection works.
- Wrap pipelines in a domain service to hide infrastructure details from callers.
- Short-circuit by returning without calling `$next`; build pipe lists dynamically for conditional steps.
- Each pipe is independently unit-testable with a trivial closure as the `$next` stub.

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

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

  Can I use the Laravel Pipeline outside of a service provider or controller?Yes. Resolve `Illuminate\\Pipeline\\Pipeline` from the container anywhere — an action class, a queued job, a console command. It has no HTTP dependency.

   How do I pass additional context to a pipe without polluting the payload DTO?Inject the context as a constructor dependency on the pipe class. The container resolves it automatically, keeping your payload DTO focused on the domain data it carries.

   Is there a performance cost to using Pipeline over a plain method chain?The overhead is negligible — it is a foreach loop over closures. For hot paths with thousands of iterations, benchmark first, but in typical request or job contexts the difference is immeasurable.

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

[Mohamed Said](https://msaied.com#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://msaied.com#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 articleMySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode](https://msaied.com/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode)  

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

1. [The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware](#the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware)
2. [The Anatomy of Illuminate\\Pipeline\\Pipeline](#the-anatomy-of-codeilluminatepipelinepipelinecode)
3. [Building Typed Pipe Classes](#building-typed-pipe-classes)
4. [Encapsulating a Pipeline as a Domain Service](#encapsulating-a-pipeline-as-a-domain-service)
5. [Short-Circuiting and Conditional Pipes](#short-circuiting-and-conditional-pipes)
6. [Testing Individual Pipes](#testing-individual-pipes)
7. [When to Reach for a Pipeline](#when-to-reach-for-a-pipeline)
8. [Takeaways](#takeaways)

 ###  Have a technical challenge?

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

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

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

 [ ![](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://msaied.com/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://msaied.com/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) [ ![](https://cdn.msaied.com/737/f5c7e78a645325d4cff8d43f2835f136.png)  · 4 min read### Laravel Telescope Alternatives: Structured Observability with Pulse, Debugbar, and Custom Watchers

4 Oct 2026 ](https://msaied.com/articles/laravel-telescope-alternatives-structured-observability-with-pulse-debugbar-and-custom-watchers) 

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

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

 [Discuss your project ↗](https://msaied.com#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://msaied.com)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/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://msaied.com/sitemap.xml)
