dcblogdev/laravel-filters
dcblogdev/laravel-filters provides a lightweight way to filter Laravel Eloquent queries from request parameters. Define allowed filters, apply them through a reusable trait, and keep controllers clean with composable, testable filtering logic.
dcblogdev/laravel-filters is a small, focused package that turns request query strings into Eloquent query constraints, so you can drop hand-rolled $request->has('status') chains from your controllers.
composer require dcblogdev/laravel-filters. Package auto-discovery means there is normally no manual service-provider registration; check the README for any optional config/asset publishing step in the version you install.php and laravel/framework constraints are compatible before bumping, and run your test suite after the upgrade — the release itself contains only dependency bumps and no library code changes, so it should be a drop-in.GET /posts?status=published&sort=-created_at, apply the package's filter in the controller, and return the paginated result. That is the highest-value, lowest-risk place to start, because it replaces boilerplate you would otherwise write by hand in every controller.A good rollout order: one read-only listing endpoint → feature tests covering representative query strings → then wider adoption across other index screens.
Trait on the model, allowlist in the model. The healthiest pattern is to attach the package's filterable behaviour to the model and declare the columns that may be filtered alongside it. Keeping the allowlist on the model (or a dedicated filter class) rather than in the controller keeps the contract close to the schema and prevents controllers from drifting.
Thin controllers. The controller should only do three things: validate/authorise the request, hand the query string to the filter, and return the paginated result. All column mapping, sorting rules and default ordering belong in the model or filter definition.
// Controller shape — details depend on the package's API, confirm in README
public function index(Request $request)
{
$posts = Post::filter($request->query()) // pseudo-API
->with('author')
->paginate(25)
->withQueryString();
return view('posts.index', compact('posts'));
}
Pair it with Form Requests. Even though the package will ignore unknown keys, validating the shape of filter input (sort in an allowlist, status in an enum, dates parseable) gives you clean 422s and makes the frontend contract explicit. Validation and filtering are complementary, not redundant.
Sorting as a first-class citizen. Most real screens need ?sort=-created_at&sort=title. Decide early whether the package handles multi-column sort or whether you layer a small orderBy resolver on top; mixing both ad hoc is where listings start behaving inconsistently between screens.
Reuse across HTTP and non-HTTP callers. Because the filter is model-level, the same definition can back a JSON API controller, a Blade index view, and an internal report command. Pass an array of parameters rather than reaching for request() inside the model so the definition stays testable.
Test with real query strings. Feature tests of $this->get('/posts?status=published&sort=-created_at') catch the regressions that unit tests miss — unknown keys, empty values, arrays vs. scalars, and ordering.
Complement, don't duplicate. If you already use a larger filtering library, don't run both on the same model. Pick one owner of query-string → query-builder translation.
orderBy/where column position. Always constrain filterable and sortable columns to an explicit allowlist; the package cannot know your schema.?status= and ?status[]=a&status[]=b are the classic breakage points. Confirm how your version handles empty strings (ignore vs. where status = '') and array input, and add a test for each.withQueryString() on the paginator, otherwise page 2 silently drops the filters and looks like a package bug.->toSql() / ->getBindings(), or register a DB::listen() in a Tinker session. That tells you immediately whether the filter applied nothing, applied the wrong column, or applied correctly and the problem is elsewhere (e.g. a global scope or soft deletes).symfony/http-foundation, league/commonmark, laravel/framework 10.48.29, phpunit 9.6.33, symfony/process, psy/psysh) plus Laravel 13 support. phpunit and psysh are dev-only and do not affect your runtime; the runtime-relevant bumps are transitive Symfony/Laravel constraints. No deprecations or breaking API changes are announced, so upgrading is low risk — but if you pin transitive versions or run Laravel 10 with a tight lockfile, composer update dcblogdev/laravel-filters -W and re-run tests rather than assuming.^2.0 is appropriate for a package at this maturity; avoid dev-main in production, since a dependency-only release like this one shows how little the maintainer surface area changes between tags.How can I help you explore Laravel packages today?