Eloquent Query Objects: Typed, Testable Builder Scopes | 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 Macro-Free Extensibility: Extending Eloquent Builder with Custom Query Objects

 Laravel Macro-Free Extensibility: Extending Eloquent Builder with Custom Query Objects
=======================================================================================

 Skip global macros and reach for typed, testable query objects that encapsulate reusable Eloquent constraints — keeping your Builder clean and your domain logic explicit.

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

ShareCopy linkCopied

 ![Laravel Macro-Free Extensibility: Extending Eloquent Builder with Custom Query Objects](https://cdn.msaied.com/621/b0c176a363378658e83bb44ed379879b.png) 

  On this page +1. [The Problem With Macro Sprawl](#the-problem-with-macro-sprawl)
2. [What Is a Query Object?](#what-is-a-query-object)
3. [Composing Multiple Query Objects](#composing-multiple-query-objects)
4. [Binding Query Objects via the Service Container](#binding-query-objects-via-the-service-container)
5. [Testing in Isolation With Pest](#testing-in-isolation-with-pest)
6. [When to Prefer Local Scopes Instead](#when-to-prefer-local-scopes-instead)
7. [Takeaways](#takeaways)

 The Problem With Macro Sprawl
-----------------------------

Macros are convenient, but registering a dozen `Builder::macro()` calls in a service provider creates invisible global state. There is no type hint, no IDE completion, and no obvious place to test them in isolation. For teams working on a modular monolith or a domain-rich application, typed query objects are a better trade-off.

What Is a Query Object?
-----------------------

A query object is a plain PHP class that accepts an `Illuminate\Database\Eloquent\Builder` instance and applies one cohesive set of constraints. It is not a repository — it does not fetch results. It only *scopes* the query, leaving the caller in control of pagination, eager loading, and execution.

```php
