Laravel cookbook · Beginner

Reuse query logic with local scopes

The same where-clauses are copied across controllers and drift apart.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

Define local scopes with the #[Scope] attribute on protected methods, then chain them. Keep scopes small and composable, and use a global scope only for rules that apply to every query (such as tenancy).

Solution

<?php
// app/Models/Product.php
namespace App\Models;

use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    #[Scope]
    protected function active(Builder $query): void
    {
        $query->where('active', true)->whereNull('discontinued_at');
    }

    #[Scope]
    protected function inCategory(Builder $query, string $slug): void
    {
        $query->whereRelation('category', 'slug', $slug);
    }

    #[Scope]
    protected function search(Builder $query, ?string $term): void
    {
        $query->when($term, fn (Builder $q) => $q->whereAny(['name', 'sku'], 'like', "%{$term}%"));
    }
}

// Usage
// Product::active()->inCategory('teapots')->search(request('q'))->orderBy('name')->paginate(20);

Pitfalls

  • Global scopes for convenience filters, surprising developers who forget withoutGlobalScope.
  • orWhere inside scopes without grouping, which breaks other constraints; wrap them in a closure.
  • Leading-wildcard LIKE searches on large tables; use full-text search.

Variations

  • Custom Eloquent builders (#[UseEloquentBuilder]) group many query methods for complex models.
  • Use whereFullText with a full-text index for search.

Further reading: Local scopes

Reference: https://laravel.com/docs/13.x/eloquent#local-scopes

More Laravel recipes

Was this page helpful?