oooiik / laravel-query-filter
A clean, convention-based way to extract Eloquent query filters into dedicated filter classes â keep your controllers and scopes thin.
Requires
- php: ^7.3|^8.0
- illuminate/console: ^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0
- illuminate/database: ^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0
- illuminate/support: ^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0
This package is auto-updated.
Last update: 2026-06-30 12:53:19 UTC
README
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();
Features
- ðŊ Convention over configuration â each public method on your filter class becomes a filter key. No registration, no metadata.
- ðŠķ Single trait + base class â add
Filterableto a model, point it at a filter class, done. - ð Artisan generator â
php artisan make:filter UserFilterscaffolds the class for you. - ð Composable â apply multiple parameter sets to the same filter instance and chain into the query.
- âïļ Defaults & fallbacks â provide default parameter values and fallback handlers for missing keys.
- ð§Đ Laravel 6 â 12 â broad compatibility, PHP 7.3+ through 8.x.
Installation
composer require oooiik/laravel-query-filter
The service provider is auto-registered via Laravel's package discovery.
Quick Start
1. Generate a filter
php artisan make:filter UserFilter
This creates app/Filters/UserFilter.php.
2. Define your filter methods
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); } }
3. Attach the filter to your model
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; }
4. Use it
// 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.
Advanced Usage
Default parameters
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.
Fallback methods
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.
Standalone filter instance (chaining)
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
Accessing all parameters
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}%"); } }
Comparison with 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.
Requirements
- PHP 7.3 or higher
- Laravel 6.x â 12.x
Compatibility Matrix
| 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 |
Contributing
Pull requests are welcome. For substantial changes, please open an issue first to discuss the direction.
Bug reports and feature ideas â GitHub Issues.
Credits
- Obidjon Toshev â author & maintainer
- All contributors
License
The MIT License (MIT). See LICENSE for details.