Laravel 13.26 allows wherePivot() and orWherePivot() to accept closures, enabling direct reuse of local scopes defined on custom Pivot models.
When querying belongsToMany relationships, filtering pivot columns traditionally required repeating raw column checks (->wherePivot('is_active', true)->wherePivot('is_muted', false)) wherever the relationship was called.
Laravel 13.26 enhances wherePivot() and orWherePivot() to accept closures, letting you invoke local scopes declared directly on your custom Pivot class.
1. Define Local Scopes on the Custom Pivot Model
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Pivot;
class ProjectSubscriber extends Pivot
{
public function scopeActive(Builder $query): Builder
{
return $query->where('is_active', true);
}
public function scopeNotMuted(Builder $query): Builder
{
return $query->where('is_muted', false);
}
}
2. Chain Scopes Directly in wherePivot()
The closure receives a builder instance bound to your custom pivot model:
// Project model relationship:
// $project->belongsToMany(User::class)->using(ProjectSubscriber::class);
// Query active, non-muted subscribers using pivot scopes
$subscribers = $project->subscribers()
->wherePivot(fn ($query) => $query->active()->notMuted())
->get();
// Also works directly with orWherePivot()
$eligible = $project->subscribers()
->wherePivot(fn ($query) => $query->active())
->orWherePivot(fn ($query) => $query->where('role', 'admin'))
->get();
Key Benefits
- Encapsulation: Keeps pivot schema details inside the
Pivotclass rather than scattering raw column names throughout controllers. - Reusability: Updating the definition of an "active" subscription updates all pivot queries across the entire application.
- Backwards Compatible: Standard scalar syntax (
wherePivot('is_active', true)) remains fully supported for simple checks.
Related Tips
View all tips →Simplify Chronological Query Sorting with latest() and oldest()
Replace verbose orderBy('created_at', 'desc') clauses with the clean latest() and oldest() query builder helpers.
Execute Custom Fallbacks on Missing Records with firstOr()
Use firstOr() to retrieve the first matching database record or execute a custom fallback closure when no record is found.