n7olkachev/laravel-filterable
A lightweight Laravel package for filtering Eloquent models using request query parameters. Define allowed filters on your model, then let the package apply them automatically, keeping controllers clean and filter logic reusable.
The headline of v2.0.0 is the new $filterable property, which exists to close the SQL-injection-style hole that open-ended filtering creates: letting a request name an arbitrary column is effectively letting the client shape your WHERE clause. Treat this release as a security-hardening upgrade, and budget time for it rather than dropping it in casually.
Minimal steps:
composer require <vendor>/<package>:^2.0 and run your test suite before touching production code — the major bump signals behavioural change, not just an additive property.$filterable declaration belongs there, next to $fillable, and should read like a deliberate contract rather than a copy of the table schema.The first use case is almost always a paginated admin or API listing where the front end sends ?name=...&status=...&created_at=.... That is exactly the surface the new property protects, so it is the right place to validate the upgrade.
Whitelist per model, not per application. Keep $filterable narrow and endpoint-driven. A User model exposing email for the support dashboard does not mean email should be filterable from the public API. If the package allows narrowing at call time, prefer declaring the full safe set on the model and further restricting it in the controller or a dedicated query object.
Pair it with your existing mass-assignment discipline. $fillable and $filterable answer different questions — what may be written versus what may be searched — but reviewers should see them side by side. If a column is sensitive enough to be excluded from $fillable, it is a strong candidate for exclusion from $filterable too.
Centralise filter parsing in a form request or query object. Validate and normalise incoming filter keys before they reach the package. This gives you a second gate in front of $filterable, produces clean 422s for unknown keys, and keeps controllers thin. A ListUsersQuery-style class that owns both the allowed keys and the eager-loading strategy scales much better than inline filtering.
Compose with pagination, sorting and scopes deliberately. Filter first, then sort, then paginate, and keep the ordering stable by always appending a tiebreaker column. When you add a scope that already constrains a column, make sure the same column being filterable does not let a caller widen the result set beyond the scope's intent.
Treat relationship filters as part of the whitelist. If the package supports filtering through relations, the $filterable entry is only half the story — the related model may also need its own declaration. Test nested filters explicitly, because this is where v2 upgrades tend to surprise people.
Write a test per declared column, plus one for a rejected column. A small dataset provider that asserts each whitelisted key works and that ?password=... or ?is_admin=1 is ignored or rejected is cheap insurance and documents the contract for the next developer.
This is a major version for a reason. The most likely breakage is that filtering on columns absent from $filterable now yields an empty result set or throws, where v1 happily filtered on anything. Audit your front end, your API clients and any saved bookmarks or dashboards that hard-code filter keys before deploying. Grep for query-string construction in your JS and for filter[ in your test fixtures.
An unset $filterable is not a neutral default. Decide explicitly whether the package fails closed (no filters allowed) or open (everything allowed) when the property is missing, and make that decision visible in your upgrade notes. If it fails closed, a forgotten model silently returns unfiltered listings — a bug that looks like a UI problem and wastes an afternoon. If it fails open, you have not actually gained the security benefit. Check the source or the changelog rather than assuming.
Do not reach for wildcards as a shortcut. If the package supports a catch-all entry, resist it. It reinstates exactly the vulnerability the release was written to fix, and it will pass code review because it looks tidy.
Debugging: log the resolved filter key set alongside the generated SQL (DB::listen or the query log) when a filter "does nothing". Distinguishing "key rejected by the whitelist" from "key accepted but matched no rows" is the single most common time sink with this class of package.
Config quirks: if a published config file controls the property name, the exception behaviour, or a global allow-list, publish it and commit it. A config default that differs between your local and production caches (config:cache) is a classic source of "works on my machine" filter failures.
Extension points worth knowing: custom filter resolvers or per-filter callbacks let you handle ranges, date boundaries and enum mapping without widening $filterable. Prefer adding a resolver for created_at over exposing raw column access, and prefer a scope for complex predicates over encoding logic in the request layer.
Rollout tip: ship $filterable declarations and the version bump in one PR, but enable the stricter behaviour behind a config flag if the package offers one. That gives you a fast rollback path if an overlooked client starts sending filters you have not whitelisted.
How can I help you explore Laravel packages today?