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

Api Filters Bundle Laravel Package

isma/api-filters-bundle

A Laravel package for clean, query-string-driven API filtering. Turn request parameters into Eloquent queries with reusable filters, sorting, and pagination—keeping controllers thin and endpoints consistent. Simple to configure, easy to extend, and built for real-world REST APIs.

View on GitHub
Deep Wiki
Context7

Getting Started

First, calibrate expectations: isma/api-filters-bundle is a Symfony bundle, not a Laravel package. It has no laravel/framework dependency, no auto-discovered service provider, and its strategies emit Doctrine QueryBuilder expressions, not Eloquent. If your day job is 100% Laravel, the realistic value here is architectural — it is a clean reference implementation of the strategy-pattern approach to filter resolution. The direct Laravel equivalents you would actually ship are spatie/laravel-query-builder (whitelist-based, ?filter[...] and ?sort= out of the box) or a small in-house FilterStrategy set built on Eloquent scopes. Read this package's source the way you'd read a design doc.

If you are on a Symfony service (or a mixed team), these are the minimal steps:

  1. Install and register. composer require isma/api-filters-bundle, then add the bundle to config/bundles.php. There is no Flex recipe — assume you wire it manually and run bin/console cache:clear afterwards. Pin a version: as of v1.3.0 the package is iterating (v1.2.1 → v1.3.0 shipped within one cycle), so ^1.3 is safer than *.
  2. Find the extension point before you write code. Read, in this order: the README's usage snippet, src/DependencyInjection/ (this tells you the config tree and therefore the exact YAML keys), then the directory holding the strategy classes. The bundle's whole contract is: allowed filter definitions in → HTTP query params resolved → matching strategy invoked against a query builder. Ten minutes in DependencyInjection/Configuration.php will save you an hour of guessing key names, because a low-star package's README drifts from its code — and it just grew three new operator names that the docs may lag behind.
  3. Configure a whitelist. The pattern is a per-resource map of filter name → strategy/operator. Do not start from "accept anything"; start from three fields.
  4. First use case — a list endpoint. A controller that takes Request $request and a QueryBuilder for Product, hands both to the bundle's resolver, and returns the paginated result:
GET /api/products?filter[name][ilike]=widget&filter[price][gte]=100&filter[status][ieq]=active

That single endpoint is the entire value proposition: the front-end composes filter params freely, the back-end stays in control of which columns and operators are legal.

Where to look first, concretely: src/ for the strategy interface (your extension point), Resources/config/ or config/ for the service definitions (where strategies get tagged), and the tests — with 0 dependents and a single maintainer, the test suite is the most reliable spec you have. For v1.3.0 specifically, open the strategies for ilike, ieq, and inoteq first: they are the newest code, they are where the case-normalisation behaviour lives, and PR #11/#12 are recent enough that edge cases (NULLs, wildcards, non-ASCII) may not be covered.

Implementation Patterns

Whitelist-per-resource, not global. Define a filter set per entity/endpoint rather than one giant config. A Product endpoint should not be able to filter on User.password_changed_at just because a strategy exists for it. This is the single most important pattern in the package and the one most easily lost in translation.

One strategy per operator, composed not branched. The healthy shape is:

  • eq, ieq, like, ilike, gte/lte, in, inoteq, between, isNull as distinct strategy classes
  • each strategy receives the QueryBuilder, the field, and the raw value, and returns a modified builder
  • the resolver's job is purely mapping the request param to a strategy, never building expressions itself

When you extend it, add a class rather than a switch. That is what makes the bundle worth copying into Laravel: your Eloquent version becomes scopeEq, scopeIlike, scopeBetween, resolved from a static $allowedFilters array — same shape, no Doctrine.

Treat the case-insensitive family as opt-in per field. v1.3.0's ieq / ilike / inoteq are the right operators for human-entered identity fields — filter[email][ieq], filter[name][ilike], filter[username][ieq]. They are the wrong default for everything else: they almost certainly compile to LOWER(col) LIKE LOWER(:val) (or a LOWER()-wrapped compare) because ILIKE is Postgres-only, which means no plain B-tree index. Enable them on the handful of columns that need them and keep eq/like on the rest.

Compose with sorting and pagination at the edge, not inside strategies. Let filters narrow the builder, then apply orderBy and pagination in the controller or a dedicated query-object. Mixing pagination into a filter strategy makes both untestable.

Feature-test the HTTP contract, unit-test the strategies. The resolver is thin; the risk lives in param parsing (filter[price][gte]=foo, nested arrays, missing operator, unknown operator name). Write functional tests asserting the emitted SQL/DQL or the result set for a handful of param shapes, and keep a small unit test per strategy — with null values, mixed case, and values containing %/_.

Adapter layer if you must bridge to Laravel. If you genuinely want this bundle's config format in a Laravel app, write a thin adapter: parse the same filter[field][operator]=value shape, resolve against the same whitelist, and dispatch to Eloquent scopes instead of Doctrine strategies. Keep the adapter in one namespace (App\Queries\Filters) so it doesn't leak into models. The new operators port cleanly — Eloquent's whereRaw('LOWER(col) LIKE ?') plus a Str::lower() binding is the whole implementation.

Integration tip for Symfony teams: register custom strategies with the tag the bundle expects (check the compiler pass in DependencyInjection/) and inject any repository/authorization service into the strategy constructor. A strategy that can consult the current user is how you get row-level filtering (filter[tenant] resolving to "current user's tenant" rather than trusting the client) instead of client-controlled scoping.

Gotchas and Tips

  • This is unvetted, low-traffic code. 13 stars, 0 dependents, one maintainer. It is now clearly active — v1.2.1 through v1.3.0 within a short window — but treat it as vendored code: read every line you rely on, pin the exact version in composer.json, and be prepared to fork. Do not put it on a critical path you can't patch yourself. Newly added strategies are the least-battle-tested code in the tree; audit ilike/ieq/inoteq yourself before trusting them in production.
  • Symfony-only surface area. config/bundles.php, YAML config tree, Doctrine QueryBuilder, service tags. Any tutorial or Stack Overflow answer you find for "Laravel API filters" will not map. Budget for translation, not copy-paste.
  • ILIKE is not portable — check how v1.3.0 actually implements it. Postgres has native ILIKE; MySQL, MariaDB and SQLite do not. If the strategy emits raw ILIKE, you have a Postgres-only bundle and it will blow up on other drivers. If it wraps in LOWER(), you have a silent index bypass. There is no third option that is free, so decide which trade-off you're buying. Also test non-ASCII: LOWER() on some collations doesn't case-fold É/İ the way a user expects.
  • inoteq and NULL columns. LOWER(col) <> LOWER(:val) returns NULL — not true — when col IS NULL, so those rows vanish from the result set. That's SQL-correct and usually product-wrong. If nullable columns are filterable with a negation, add an explicit OR col IS NULL or document the behaviour loudly.
  • Pattern metacharacters in like/ilike. ?filter[name][like]=100% is not a literal percent unless the strategy escapes % and _. Verify escaping; if it's absent, a user-supplied value can turn a targeted like into a table scan, and ?filter[x][ilike]=% matches everything.
  • Operator allowlisting is a security control, not a nicety. If an unknown operator falls back to eq silently, a typo'd front-end request returns wrong data with a 200. Prefer an explicit 400 for unknown filter or operator keys — loud failures beat quietly-wrong result sets. This matters more now that there are more valid operator strings to typo (ieq vs eq vs inoteq is a one-character, data-changing difference).
  • Unknown-field behaviour is the classic trap. Verify what happens with ?filter[does_not_exist][eq]=1. Whichever way it behaves, decide deliberately and test it; silent ignore vs. exception is a client-breaking contract either way.
  • Join/relation strategies are where N+1 and duplicate rows appear. A filter on a to-many relation without distinct will return duplicated parents. Test counts, not just "it returned rows."
  • Debugging: log the resolved filter set (field → operator → value) before the builder is executed, and dump the DQL. Nine times out of ten the bug is param shape mismatch between front-end and whitelist, not the strategy — and now also a case-sensitivity mismatch between what the client sent and what the DB compares.
  • Config caching bites in Symfony. After editing the filter config, bin/console cache:clear; in dev, stale container config produces "no filters applied" behaviour that looks like a package bug. New operator names are config-tree keys, so this applies to the v1.3.0 upgrade too — clear the cache before concluding the upgrade failed.
  • Extension point to remember: the strategy interface is the seam. Custom strategies are the sanctioned way to add operators, relation-aware filtering, or user-scoped filters — subclassing the resolver is not. If the shipped ilike doesn't handle your driver or your collation, write your own strategy rather than patching the resolver.
  • If you're Laravel-first, take the design, not the dependency. Port the whitelist + strategy model into ~200 lines of Eloquent scopes, or use spatie/laravel-query-builder. You get the same front-end contract with a maintained package and no Doctrine in your stack — and Eloquent's whereLike($col, $val, caseSensitive: false) covers the new operator family natively.
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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo