Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filterable Filters Laravel Package

tomodo531/filterable-filters

A Laravel package for reusable, request-driven Eloquent filters. tomodo531/filterable-filters helps you define filter classes and apply them consistently, keeping controllers and models clean while making complex filtering logic easier to organize, compose, and maintain.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a two-star, single-release package from December 2022 with no dependents. That context should shape your first hour: read the source before you write any application code. The entire package is almost certainly a handful of files — a service provider, a Filterable trait, and a base Filter/QueryFilter class. Open vendor/tomodo531/filterable-filters/src and skim all of it before continuing. You need to know three things: the trait's name and namespace, the base class contract (which method you override to build a query), and whether a config file is published.

Install and inspect:

composer require tomodo531/filterable-filters
composer show tomodo531/filterable-filters   # confirm version + Laravel constraint
php artisan vendor:publish --provider="Tomodo531\FilterableFilters\FilterableFiltersServiceProvider"

Check composer.json inside the package for illuminate/support and php constraints. If it pins ^8.0|^9.0, you are fine on Laravel 9/10 with --ignore-platform-reqs at worst, but a Laravel 11/12 app will need a fork or a replace entry.

First use case: a filtered index endpoint. Take your noisiest listing route — GET /api/orders?status=paid&customer=acme&created_after=2024-01-01 — and move its ad-hoc ->when() chain into a single dedicated filter class. That is the one place where the package earns its keep: it turns a growing pile of conditional when() calls in a controller into one testable class.

If vendor/tomodo531/filterable-filters turns out to be an empty shell or a stub, stop and use spatie/laravel-query-builder instead. With 0 dependents and one release, the risk of it being an abandoned experiment is real, and the migration cost later is higher than the switch cost now.

Implementation Patterns

One filter class per resource, not per endpoint. Put them in app/Filters and let a single OrderFilter serve the API index, the admin table, and the CSV export. The package's trait almost certainly resolves filter classes by convention or by an overridable property; either way, keep the mapping explicit so a new query string key cannot silently reach the database.

Whitelist by construction. The core safety property of any filterable package is that unknown query params are ignored. Verify that by sending ?password=1&id=5 at a model whose filter class does not define those keys, and watch ->toSql(). If the package passes params straight through, wrap it: validate with a Form Request first, then hand only the validated array to the filter.

Validation belongs in a Form Request, not the filter. Filters should receive already-typed data. Do the status => in:paid,pending and created_after => date work in the request class so the filter stays a pure query builder you can unit-test without HTTP.

// Controller — thin
public function index(OrderIndexRequest $request, OrderFilter $filter)
{
    return OrderResource::collection(
        $filter->apply(Order::query())->paginate(25)
    );
}

Compose with sorting and pagination deliberately. Filtering, sorting, and pagination are three separate concerns that all mutate the same builder. Pick an evaluation order and keep it: filter → sort → eager-load → paginate. If sorting is not in this package, apply it after filtering so the filtered set is what gets ordered, and use a column allowlist for the sort key exactly as you did for filters.

Relationship filters are where the value is. ?customer=acme should become whereHas('customer', fn ($q) => $q->where('name', 'acme')), not a join. Keep relationship filters in the same class as column filters so the with() calls that prevent N+1 live next to the whereHas() calls that created the need for them.

Test the filter class directly. Instantiate it with an array, apply it to a Model::query(), assert on ->toSql() and bindings for shape, then assert on actual results for correctness. That gives you fast regression coverage without booting HTTP, which matters a lot for a package you may have to fork.

Gotchas and Tips

  • Falsy query strings. ?page=0, ?active=0, and ?q= arrive as strings. A naive if ($value) skips "0" entirely, so boolean and numeric filters break silently. Use filled() / !== null && !== '' checks in your filter methods.
  • Array params. ?status[]=paid&status[]=pending gives you an array. Cast and validate it in the Form Request, and make the filter explicitly handle is_array($value) — mixing scalar and array handling in one whereIn is a common source of Array to string conversion errors.
  • LIKE filters kill indexes. A free-text ?q= mapped to where('name', 'like', "%$v%") will not use an index and will scan. For anything non-trivial, move to a full-text index, ->whereFullText(), or Scout — and keep that decision out of the shared filter class so listing pages stay fast.
  • Unvalidated column names. If any part of the package lets you pass a column name (?sort=, or a filter that maps a key to $request->input($key) as a column), you have SQL injection or at minimum schema disclosure. Never let a request value become a column identifier. Allowlist it.
  • Maintenance risk. Released once in December 2022, 2 stars, 0 dependents. Treat it as vendored code: fork it into app/Support/FilterableFilters or a private package if you depend on it, and pin the version in composer.json ("tomodo531/filterable-filters": "1.0.*", never *). A composer update should not be able to change your query layer.
  • Config caching. If the package publishes a config file, php artisan config:cache will freeze it. If you edit the config and see old behavior, run php artisan config:clear before debugging anything else.
  • Debugging the generated query. ->toSql() shows placeholders; use ->ddRawSql() (Laravel 9+), or DB::listen(fn ($q) => logger($q->sql)) to see every query the filters produced. When results are wrong but the SQL looks right, the bug is almost always in the bindings order or in a whereHas that got nested one level too deep.
  • Extension points to prefer. If the package exposes a base filter class, extend it once in your app (app/Filters/Filter.php) and extend that everywhere. You then have a single place to add cross-cutting behavior — tenant scoping, soft-delete handling, default with() — without touching the vendor directory, which survives the fork you will probably need.
  • Multi-tenancy. Filters run as a chained builder on top of whatever query you pass in. Always pass a pre-scoped query (Tenant::query()/a global scope) into the filter rather than letting the filter start from Model::query(); otherwise a whereHas filter can leak rows across tenants.
  • Empty filter sets. Confirm the package returns the original builder unchanged when no params are present — some implementations return early with a fresh query and silently drop the eager loads you chained before calling them. Call apply() last if you are unsure.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin