Eloquent Query Scopes: Global, Local &amp; Pending Internals | 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 at Scale: Global, Local, and Pending Scope Internals

 Eloquent Query Scopes at Scale: Global, Local, and Pending Scope Internals
===========================================================================

 Go beyond basic where() calls. Learn how Eloquent's scope pipeline works internally, when global scopes silently break joins, and how to write composable local scopes that stay testable under load.

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

ShareCopy linkCopied

 ![Eloquent Query Scopes at Scale: Global, Local, and Pending Scope Internals](https://cdn.msaied.com/278/4614d18b4735498f765593b468cbe591.png) 

  On this page +1. [Eloquent Query Scopes at Scale](#eloquent-query-scopes-at-scale)
2. [How Eloquent Applies Scopes Internally](#how-eloquent-applies-scopes-internally)
3. [Global Scope Pitfall: Ambiguous Columns on Joins](#global-scope-pitfall-ambiguous-columns-on-joins)
4. [Local Scopes: Composable by Design](#local-scopes-composable-by-design)
5. [The withoutGlobalScope Escape Hatch](#the-codewithoutglobalscopecode-escape-hatch)
6. [Pending Scopes and Eager Loading](#pending-scopes-and-eager-loading)
7. [Takeaways](#takeaways)

 Eloquent Query Scopes at Scale
------------------------------

Scopes feel simple until they silently corrupt a join, double-apply a `where`, or make a multi-tenant query leak rows. Understanding what happens inside `Builder::applyScopes()` changes how you design them.

### How Eloquent Applies Scopes Internally

Every `Model::query()` call returns an `Illuminate\Database\Eloquent\Builder` wrapping a base `QueryBuilder`. Global scopes are stored on the model class and applied lazily — not when you call `query()`, but when the builder is about to compile SQL. The entry point is `Builder::applyScopes()`:

```php
// Simplified from Illuminate\Database\Eloquent\Builder
public function applyScopes(): static
{
    foreach ($this->model->getGlobalScopes() as $identifier => $scope) {
        if (! isset($this->removedScopes[$identifier])) {
            $scope->apply($this, $this->model);
        }
    }
    return $this;
}

```

This lazy evaluation is why you can call `withoutGlobalScope(TenantScope::class)` after `Model::query()` and it still works — the scope hasn't run yet.

### Global Scope Pitfall: Ambiguous Columns on Joins

The classic trap: your `TenantScope` adds `->where('tenant_id', tenant())`. Then a join brings in another table with a `tenant_id` column.

```php
class TenantScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        // BAD: ambiguous when joined
        $builder->where('tenant_id', tenant()->id);
    }
}

```

PostgreSQL and MySQL both throw `Column 'tenant_id' in where clause is ambiguous`. Fix it by qualifying the column:

```php
public function apply(Builder $builder, Model $model): void
{
    $table = $model->getTable();
    $builder->where("{$table}.tenant_id", tenant()->id);
}

```

Always qualify columns in global scopes. It costs nothing and prevents production incidents.

### Local Scopes: Composable by Design

Local scopes should be narrow and composable, not a grab-bag of business logic. A scope that does too much becomes impossible to reuse.

```php
class Order extends Model
{
    public function scopePending(Builder $query): Builder
    {
        return $query->where('status', OrderStatus::Pending);
    }

    public function scopeOlderThan(Builder $query, Carbon $date): Builder
    {
        return $query->where('created_at', 'for(Customer::factory()->create())
        ->create();

    $loaded = Order::with('customer')->find($order->id);

    expect($loaded->customer)->not->toBeNull();
});

```

### Takeaways

- Global scopes are applied lazily at SQL compile time, so `withoutGlobalScope()` works even after `query()`.
- Always qualify column names in global scopes to prevent ambiguity on joins.
- Keep local scopes narrow and single-purpose; compose them at the call site.
- Eager-loaded relationships get their own builder — global scopes on related models apply independently.
- Every `withoutGlobalScopes()` call deserves a comment; treat it as a deliberate bypass, not a convenience.

- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [eloquent](https://www.msaied.com/public/articles?search=eloquent)
- [database](https://www.msaied.com/public/articles?search=database)
- [architecture](https://www.msaied.com/public/articles?search=architecture)

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

  Can a global scope cause problems with Eloquent eager loading?Not usually — eager loading creates a fresh builder for the related model, so global scopes on that model apply correctly. The risk is when a global scope uses unqualified column names and the eager load involves a join internally, which can cause ambiguity errors.

   Is it safe to call withoutGlobalScope() inside a repository method?Yes, but document it clearly. Because scopes are applied lazily, calling withoutGlobalScope() before the query executes is always safe. The danger is accidentally bypassing tenant isolation — restrict such calls to explicitly privileged service classes and cover them with tests.

   Do local scopes work with cursor() and lazy collections?Yes. cursor() and lazyById() both go through the same Eloquent builder pipeline, so all applied local and global scopes are respected. The difference is in how rows are hydrated — one at a time via a generator — not in how the WHERE clauses are built.

   ![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 articleFilament v5 Preview: Schema Unification, Performance Shifts, and How to Prepare Now](https://www.msaied.com/public/articles/filament-v5-preview-schema-unification-performance-shifts-and-how-to-prepare-now) [Next articleYammi Audit Log: Track Who Really Made a Change Across Laravel Jobs and Queues](https://www.msaied.com/public/articles/yammi-audit-log-track-who-really-made-a-change-across-laravel-jobs-and-queues)  

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

1. [Eloquent Query Scopes at Scale](#eloquent-query-scopes-at-scale)
2. [How Eloquent Applies Scopes Internally](#how-eloquent-applies-scopes-internally)
3. [Global Scope Pitfall: Ambiguous Columns on Joins](#global-scope-pitfall-ambiguous-columns-on-joins)
4. [Local Scopes: Composable by Design](#local-scopes-composable-by-design)
5. [The withoutGlobalScope Escape Hatch](#the-codewithoutglobalscopecode-escape-hatch)
6. [Pending Scopes and Eager Loading](#pending-scopes-and-eager-loading)
7. [Takeaways](#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/743/8998fac3a41451ab3fe1588194e17a43.png) Filament · 3 min read### Securing Filament Plugins with Plumb: Automated Security Scoring for PHP Packages

5 Oct 2026 ](https://www.msaied.com/public/articles/securing-filament-plugins-with-plumb-automated-security-scoring-for-php-packages) [ ![](https://cdn.msaied.com/742/2d02018669cdeedccb5de2efb898f0ee.png) Filament · 3 min read### Filament v3.3.56 Released: File Hash Names and Livewire Upload Fix

5 Oct 2026 ](https://www.msaied.com/public/articles/filament-v3356-released-file-hash-names-and-livewire-upload-fix) [ ![](https://cdn.msaied.com/741/5b55c123ad08e4d34e1f4b99ad6a428b.png)  · 3 min read### Filament v4 Schema-Based Forms, Infolists, and the Unified Schema API

5 Oct 2026 ](https://www.msaied.com/public/articles/filament-v4-schema-based-forms-infolists-and-the-unified-schema-api-5) 

  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)
