Laravel scopes for reusable query logic

Carlos Mendez Jan 2026
2 tabs
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Post extends Model
{
    use SoftDeletes;

    // Local scope
    public function scopePublished(Builder $query): void
    {
        $query->whereNotNull('published_at')
            ->where('published_at', '<=', now());
    }

    public function scopeFeatured(Builder $query): void
    {
        $query->where('is_featured', true);
    }

    public function scopeByAuthor(Builder $query, int $authorId): void
    {
        $query->where('user_id', $authorId);
    }

    public function scopePopular(Builder $query, int $minViews = 1000): void
    {
        $query->where('views', '>=', $minViews)
            ->orderBy('views', 'desc');
    }

    public function scopeRecent(Builder $query, int $days = 7): void
    {
        $query->where('created_at', '>=', now()->subDays($days));
    }

    public function scopeSearch(Builder $query, ?string $term): void
    {
        if (!$term) {
            return;
        }

        $query->where(function ($q) use ($term) {
            $q->where('title', 'like', "%{$term}%")
              ->orWhere('body', 'like', "%{$term}%");
        });
    }

    // Global scope - applies to all queries
    protected static function booted(): void
    {
        // Only show posts from active users
        static::addGlobalScope('activeAuthor', function (Builder $query) {
            $query->whereHas('author', fn ($q) => $q->where('status', 'active'));
        });
    }
}
2 files · php Explain with highlit

Query scopes encapsulate common query constraints into reusable methods on models. Local scopes prefix methods with scope and accept a query builder plus additional parameters. I chain scopes fluently—Post::published()->featured()->get(). Global scopes apply to all queries automatically, useful for soft deletes or multi-tenancy. Anonymous global scopes use closures in the booted() method. Scopes keep controllers clean by moving query logic to models where it belongs. Dynamic scopes accept parameters for flexible filtering. The combination of local and global scopes creates a powerful query API specific to each model. This pattern is fundamental to readable, maintainable Eloquent queries.