A clean, convention-based way to extract Eloquent query filters into dedicated filter classes — keep your controllers and scopes thin.
oooiik/laravel-query-filter is a Laravel package for a clean, convention-based way to extract eloquent query filters into dedicated filter classes — keep your controllers and scopes thin..
It currently has 0 GitHub stars and 1.148 downloads on Packagist (latest version 1.3.0).
Install it with composer require oooiik/laravel-query-filter.
Discover more Laravel packages by oooiik
or browse all Laravel packages to compare alternatives.
Last updated
A clean, convention-based way to extract Eloquent query filters into dedicated filter classes. Keep your controllers thin, your scopes focused, and your filtering logic testable.
// Before — filtering logic leaking into the controller
$users = User::query()
->when($request->username, fn($q, $v) => $q->where('username', $v))
->when($request->role, fn($q, $v) => $q->whereHas('role', fn($r) => $r->where('title', $v)))
->when($request->created_after, fn($q, $v) => $q->where('created_at', '>=', $v))
->paginate();
// After — one line, all filtering in UserFilter
$users = User::filter($request->validated())->paginate();
Filterable to a model, point it at a filter class, done.php artisan make:filter UserFilter scaffolds the class for you.composer require oooiik/laravel-query-filter
The service provider is auto-registered via Laravel's package discovery.
php artisan make:filter UserFilter
This creates app/Filters/UserFilter.php.
Each public method becomes a filter key matching its name:
namespace App\Filters;
use Oooiik\LaravelQueryFilter\Filters\QueryFilter;
class UserFilter extends QueryFilter
{
public function username($username)
{
$this->builder->where('username', $username);
}
public function role($role)
{
$this->builder->whereHas('role', function ($query) use ($role) {
$query->where('title', $role);
});
}
public function createdAfter($date)
{
$this->builder->where('created_at', '>=', $date);
}
}
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Oooiik\LaravelQueryFilter\Traits\Model\Filterable;
use App\Filters\UserFilter;
class User extends Model
{
use Filterable;
protected $defaultFilter = UserFilter::class;
}
// Controller
public function index(Request $request)
{
$validated = $request->validate([
'username' => 'nullable|string',
'role' => 'nullable|string',
'createdAfter' => 'nullable|date',
]);
return User::filter($validated)->paginate();
}
Missing keys are silently ignored — only the filter methods that match input parameters run.
Use the $default property to pre-fill values when a key is missing from the input:
class UserFilter extends QueryFilter
{
public $default = [
'status' => 'active',
'sort' => 'created_at',
];
public function status($status)
{
$this->builder->where('status', $status);
}
public function sort($column)
{
$this->builder->orderBy($column, 'desc');
}
}
Calling User::filter([]) will still apply status = active and sort by created_at desc.
Use $fallback to redirect missing input keys to a different method:
class UserFilter extends QueryFilter
{
public $fallback = [
'search' => 'searchByName',
];
public function searchByName($value)
{
$this->builder->where('name', 'like', "%{$value}%");
}
}
If the search key is missing from input, searchByName runs with whatever value was provided as the fallback source.
Apply multiple parameter sets to the same filter:
$filter = User::createFilter(UserFilter::class);
$filter->apply(['role' => 'admin']);
$filter->apply(['status' => 'active']);
$query = $filter->query();
// Both filter sets are now applied to the builder
Filter methods receive the full parameter array as a second argument:
public function username($username, $allParams)
{
if (! empty($allParams['exact_match'])) {
$this->builder->where('username', $username);
} else {
$this->builder->where('username', 'like', "%{$username}%");
}
}
spatie/laravel-query-builder| | laravel-query-filter | spatie/laravel-query-builder |
|---|---|---|
| Approach | Convention-based — method = filter key | Declarative — register allowed filters explicitly |
| Per-model class | Yes, dedicated filter class | Optional, often inline |
| Custom filter logic | Plain PHP method | AllowedFilter::callback() |
| Best for | Complex filtering with reusable logic | API endpoints with simple filtering needs |
Both are great — choose laravel-query-filter when you want a dedicated class per model with reusable, testable filter logic.
| Laravel | PHP | Status | |---|---|---| | 12.x | 8.2+ | ✅ Supported | | 11.x | 8.2+ | ✅ Supported | | 10.x | 8.1+ | ✅ Supported | | 9.x | 8.0+ | ✅ Supported | | 8.x | 7.3+ | ✅ Supported | | 7.x | 7.3+ | ✅ Supported | | 6.x | 7.3+ | ✅ Supported |
Pull requests are welcome. For substantial changes, please open an issue first to discuss the direction.
Bug reports and feature ideas → GitHub Issues.
The MIT License (MIT). See LICENSE for details.