Eloquent Query Scopes — Global and Local Scopes in Laravel
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
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