Composable Eloquent Query Scopes in Laravel | 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. Eloquent Query Scopes as First-Class Objects: Reusable, Testable, and Composable

 Eloquent Query Scopes as First-Class Objects: Reusable, Testable, and Composable
=================================================================================

 Move beyond inline where() chains by encapsulating query logic in dedicated scope classes. Learn how to build composable, testable Eloquent scopes that keep your repositories and controllers clean.

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

ShareCopy linkCopied

 ![Eloquent Query Scopes as First-Class Objects: Reusable, Testable, and Composable](https://cdn.msaied.com/201/4de012f75c1e4a61467844d2babacdfb.png) 

  On this page +1. [The Problem With Inline Query Logic](#the-problem-with-inline-query-logic)
2. [Anatomy of a Scope Class](#anatomy-of-a-scope-class)
3. [Applying Scopes Without Polluting the Model](#applying-scopes-without-polluting-the-model)
4. [Composing Scopes With a Pipeline](#composing-scopes-with-a-pipeline)
5. [Testing Scopes in Isolation](#testing-scopes-in-isolation)
6. [Global Scopes as Scope Classes](#global-scopes-as-scope-classes)
7. [Takeaways](#takeaways)

 The Problem With Inline Query Logic
-----------------------------------

Most Laravel codebases start with local scopes on models — `scopeActive`, `scopeForTenant`, `scopePublished`. They work fine until the same logic needs to appear on three different models, or until a scope grows complex enough that you want to unit-test it in isolation. At that point, the model becomes a dumping ground.

The fix is treating query scopes as first-class objects: plain PHP classes that receive a builder, apply constraints, and can be composed, reused, and tested without booting a model.

Anatomy of a Scope Class
------------------------

Start with a simple contract:

```php
namespace App\Scopes;

use Illuminate\Database\Eloquent\Builder;

interface Scope
{
    public function apply(Builder $builder): void;
}

```

A concrete implementation stays focused:

```php
namespace App\Scopes;

use Illuminate\Database\Eloquent\Builder;

final class PublishedScope implements Scope
{
    public function __construct(
        private readonly \DateTimeInterface $before,
    ) {}

    public function apply(Builder $builder): void
    {
        $builder
            ->whereNotNull('published_at')
            ->where('published_at', '
