Skip to content

Eloquent Query Scopes — Global and Local Scopes in Laravel

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Eloquent Query Scopes. We cover key concepts, practical examples, and best practices to help you master this topic.

Laravel Eloquent query scopes provide reusable query constraints with global scopes applying automatically to all queries and local scopes calling through scope methods.

What You'll Learn

By the end of this tutorial, you'll implement global and local scopes, create dynamic scopes with parameters, use scope methods via traits, and optimize query organization.

Why Scopes Matter

Scopes encapsulate query logic in the model layer instead of repeating conditions in controllers. Global scopes enforce data separation while local scopes provide reusable query filters.

Real-World Use

A multi-tenant SaaS uses a global scope to filter by tenant_id. A blog uses local scopes for published(), recent(), and byAuthor() filters callable from any controller.

Scopes Path

flowchart LR
  A[Eloquent ORM] --> B[Query Scopes]
  B --> C[Global Scopes]
  B --> D[Local Scopes]
  B --> E[Dynamic Scopes]
  B --> F[Scope Traits]
  B --> G{You Are Here}
  style G fill:#f90,color:#fff

Local Scopes

Define reusable query constraints on the model.

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class Post extends Model {
    public function scopePublished(Builder $query): Builder {
        return $query->where("status", "published");
    }
    public function scopeRecent(Builder $query, int $limit = 10): Builder {
        return $query->orderBy("created_at", "desc")->limit($limit);
    }
    public function scopeByAuthor(Builder $query, int $userId): Builder {
        return $query->where("author_id", $userId);
    }
}
// Usage
$posts = Post::published()->recent(5)->get();
$userPosts = Post::byAuthor(1)->published()->get();

Global Scopes

Apply constraints automatically to all queries.

<?php
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class TenantScope implements Scope {
    public function apply(Builder $builder, Model $model): void {
        $builder->where("tenant_id", auth()->user()->tenant_id);
    }
}
// Register in model booted method
class Invoice extends Model {
    protected static function booted(): void {
        static::addGlobalScope(new TenantScope());
    }
}

Anonymous Global Scopes

Simplify global scopes with closures.

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class Product extends Model {
    protected static function booted(): void {
        static::addGlobalScope("active", function (Builder $builder) {
            $builder->where("is_active", true)->whereNull("deleted_at");
        });
    }
    // Remove global scope for specific queries
    public function scopeWithInactive(Builder $query): Builder {
        return $query->withoutGlobalScope("active");
    }
}

Dynamic Scopes

Accept parameters in local scopes.

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class Order extends Model {
    public function scopeWhereTotalAbove(Builder $query, float $amount): Builder {
        return $query->where("total", ">", $amount);
    }
    public function scopeBetweenDates(Builder $query, string $start, string $end): Builder {
        return $query->whereBetween("created_at", [$start, $end]);
    }
    public function scopeWithStatus(Builder $query, string ...$statuses): Builder {
        return $query->whereIn("status", $statuses);
    }
}
// Usage
$largeOrders = Order::whereTotalAbove(500)->get();
$recentOrders = Order::betweenDates("2026-01-01", "2026-06-28")->get();
$processed = Order::withStatus("shipped", "delivered")->get();

Scope Traits

Share scopes across multiple models.

<?php
namespace App\Models\Traits;
use Illuminate\Database\Eloquent\Builder;
trait Publishable {
    public function scopePublished(Builder $query): Builder {
        return $query->where("status", "published")->whereNotNull("published_at");
    }
    public function scopeDraft(Builder $query): Builder {
        return $query->where("status", "draft");
    }
    public function scopeScheduled(Builder $query): Builder {
        return $query->where("status", "scheduled")->where("published_at", ">", now());
    }
}
class Article extends Model {
    use Publishable;
}
class News extends Model {
    use Publishable;
}

Common Mistakes

1. Global Scopes Affecting Admin Areas

Global scopes filter everything including admin queries. Use withoutGlobalScope() in admin contexts.

2. Scope Overwriting Previous Conditions

Scopes that use where() overwrite existing conditions on the same column. Use orWhere when needed.

3. Not Returning Builder from Scope

Scopes must return Builder or void. Forgetting the return breaks the fluent chain.

4. Complex Logic in Scopes

Scopes should be simple query constraints. Complex business logic belongs in services.

5. Scopes That Join Unconditionally

Joining tables in a global scope affects performance on every query. Use when() for conditional joins.

Practice Questions

1. What is the difference between global and local scopes?

Global scopes apply automatically to all queries. Local scopes are called explicitly by name.

2. How do you remove a global scope for a single query?

Chain withoutGlobalScope("scopeName") on the query builder.

3. Can receive scopes parameters?

Yes. Add parameters after the Builder parameter in the scope method definition.

4. How do you share scopes between models?

Define scopes in a trait and use it in multiple models.

5. Challenge: Create a set of scopes for an e-commerce model.

<?php
trait ProductScopes {
    public function scopeInStock(Builder $q): Builder { return $q->where("quantity", ">", 0); }
    public function scopeByCategory(Builder $q, int $catId): Builder { return $q->where("category_id", $catId); }
    public function scopePriceRange(Builder $q, float $min, float $max): Builder { return $q->whereBetween("price", [$min, $max]); }
}

FAQ

Can I have multiple global scopes on one model?

Yes. Add multiple global scopes in the booted method.

What happens if a global scope conflicts with a local scope?

Local scope conditions are added after global scopes. Both apply.

Can global scopes be removed permanently?

Use withoutGlobalScopes() or remove the scope from booted.

Are scopes the same as query builder macros?

No. Macros extend the query builder globally. Scopes are model-specific.

Can I use scopes with relationships?

Yes. Scopes work on relation queries too: $user->posts()->published()->get().

Mini Project: Multi-Tenant Scopes

Implement multi-tenancy with global and local scopes.

<?php
trait TenantScopes {
    public function scopeByTenant(Builder $q): Builder { return $q->where("tenant_id", tenant()->id); }
}
class Invoice extends Model {
    use TenantScopes;
    protected static function booted(): void {
        static::addGlobalScope("tenant", fn(Builder $q) => $q->where("tenant_id", tenant()->id));
    }
}

What's Next

Eloquent Accessors Mutators Eloquent Serialization Eloquent Eager Loading

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro