Updated June 2026. Tested on Laravel 13 and PHP 8.4.
A query scope is a reusable piece of a query that you keep on the model. Instead of repeating the same where everywhere, you give it a name and call it by name. The query reads better and you fix it in one place.
Say you keep writing this to get active users.
$users = User::where('active', 1)->get();
If that condition shows up across the app, package it as a scope. There are three kinds: local, global and dynamic.
Local scopes
A local scope is one you opt into per query. Define a method on the model prefixed with scope, taking a query builder and returning it with your condition added.
use Illuminate\Database\Eloquent\Builder;
class User extends Model
{
public function scopeActive(Builder $query): Builder
{
return $query->where('active', 1);
}
public function scopePopular(Builder $query): Builder
{
return $query->where('votes', '>', 100);
}
}
Call them without the scope prefix, and chain them freely.
$users = User::popular()->active()->orderBy('created_at')->get();
In recent Laravel you can also mark a plain method with the #[Scope] attribute instead of the scope prefix, but the prefix style above still works and is what you will see most.
Dynamic scopes
A scope can take parameters. Add them after the query argument.
public function scopeOfType(Builder $query, string $type): Builder
{
return $query->where('type', $type);
}
Then pass the value when you call it.
$admins = User::ofType('admin')->get();
Global scopes
A global scope applies to every query on a model automatically, without you asking for it each time. Laravel's own soft deletes work this way, quietly adding where deleted_at is null to your queries.
Write a class that implements the Scope interface and adds your condition.
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class ActiveScope implements Scope
{
public function apply(Builder $builder, Model $model): void
{
$builder->where('active', 1);
}
}
Attach it with the #[ScopedBy] attribute on the model.
use App\Models\Scopes\ActiveScope;
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
#[ScopedBy(ActiveScope::class)]
class User extends Model
{
//
}
Now User::all() only ever returns active users. For a simple one off condition you can skip the class and add an anonymous global scope in the model's booted method.
protected static function booted(): void
{
static::addGlobalScope('active', function (Builder $builder) {
$builder->where('active', 1);
});
}
Removing a global scope
When you do need the rows a global scope hides, take it off for that one query.
User::withoutGlobalScope(ActiveScope::class)->get();
User::withoutGlobalScope('active')->get(); // for an anonymous scope, by name
User::withoutGlobalScopes()->get(); // remove all of them
Scopes work through relationships
Scopes are not just for top level queries. They chain onto relationships too.
$user = User::find(34);
$popularPosts = $user->posts()->popular()->get();
So: local scopes for conditions you opt into, dynamic scopes when they need a parameter, and global scopes for conditions that should always apply. They keep your queries short and your conditions in one place. Questions welcome in the comments.
All comments ()
No comments yet
Be the first to leave a comment on this post.