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

Filter Laravel Package

eloquent-filter/filter

Eloquent Filter adds request-driven filtering to Laravel Eloquent models. Define allowed filters and custom filter classes, then filter queries through query-string parameters, keeping APIs, admin panels, and search endpoints clean, composable, and easy to maintain.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and put the trait on the models you want to filter:

composer require mehdi-fathi/eloquent-filter
use EloquentFilter\Filterable;

class Post extends Model
{
    use Filterable;

    public function getTitleFilter(string $title)
    {
        return $this->where('title', 'like', "%{$title}%");
    }

    public function getStatusFilter(string $status)
    {
        return $this->where('status', $status);
    }
}

The contract is simple: query-string keys map to methods named get{StudlyKey}Filter. ?status=published calls getStatusFilter('published'). Whatever the method returns is chained onto the underlying Eloquent builder, so it behaves exactly like a local scope.

Version note: as of 4.5.6 the package explicitly supports Laravel 13.x (in addition to earlier supported versions). If you are planning an upgrade, this is the minimum version to pin — older releases will not declare compatibility with Laravel 13 and may resolve against an untested framework.

First use case — a filterable index endpoint with no bespoke query-building code in the controller:

public function index(Request $request)
{
    return Post::filter($request->only(['title', 'status']))
        ->with('author')
        ->paginate(20);
}

Post::filter() with no argument reads the current request; passing an explicit array is the safer default in controllers, jobs, and tests.

Where to look first, in order: the README for the filter-naming convention, src/ for the trait that resolves filter names and applies them, and the package's own tests for the exact whitelist/ignore API your pinned version exposes. Because the package is small and has few dependents, treat the source as the specification.

Implementation Patterns

Treat filter methods as scopes with request-driven arguments. They should do one thing and return a query constraint. Anything more elaborate belongs in a service or a query object.

Centralize filters in a trait when several models share the same filterable columns (created_at, status, search), then use it alongside Filterable. This keeps models from growing a dozen near-duplicate methods.

Whitelist aggressively. Define an allow-list on the model (typically a $filterable property or a filterable() method, depending on version) so only declared filters can ever be invoked from the outside. This is your primary defense against a caller probing for an unexpected get*Filter method.

Exclude infrastructure params. Pagination, sorting, and includes are not filters. Register them via the package's ignoreRequest() hook so ?page=2 and ?sort=title never get treated as filter candidates.

Normalize inside the filter. Coerce booleans, trim strings, map enum-ish values:

public function getPublishedFilter($value)
{
    return $this->where('published_at', filter_var($value, FILTER_VALIDATE_BOOLEAN) ? '!=' : '=', null);
}

Guard optional filters with filled() so an empty ?title= doesn't force a LIKE '%%' scan or an unwanted WHERE.

Compose with the rest of the builder. Because filters return the builder, you can interleave with(), when(), and ordering around filter() freely.

Relation filters use dotted keys (?author.name=ali) mapped to relation-aware filter methods; keep them explicit and eager-load the relation to avoid N+1.

Testing: skip HTTP entirely and pass arrays — Post::filter(['status' => 'draft'])->toSql() — which makes filter tests fast and deterministic and avoids the no-request failure mode.

Controller pattern: keep the controller at one line (Model::filter($request->only(...))), and let the model own the semantics. This is the package's whole value proposition: listing endpoints become declarative.

Framework-upgrade pattern: if you are moving to Laravel 13, bump this package to ^4.5.6 in the same PR as the framework constraint change, then run your filter test suite against the new major. Because filters are plain builder calls, the realistic breakage surface is small — mainly deprecations in the query builder and any change to request handling — but do not bundle the two upgrades without a green test run.

Gotchas and Tips

  • Name derivation is the #1 source of silent failures. user_name in the query string becomes getUserNameFilter, not getUser_nameFilter. If a filter "isn't firing," dump the compiled query and verify the method name spelling and casing first.
  • Empty strings still invoke filters. A blank query param will call your method with ''. Always short-circuit with filled() unless you genuinely want the empty case.
  • filter() without arguments depends on a live request. In queued jobs, Artisan commands, and some test contexts there is no request binding, so the call becomes a no-op or an error. Prefer passing the array explicitly.
  • Never interpolate raw input into whereRaw. Bind parameters; the package passes your value through untouched, so injection safety is entirely your responsibility inside the filter method.
  • Return type matters. Return the builder (or a constraint via $this->where(...)). Returning something else — a boolean, a collection, null from a conditional branch — breaks the chain in ways that are hard to trace. Always return a query builder on every path.
  • Whitelist and ignore-list can mask each other. If a key is in both, behavior depends on precedence in the version you installed. Keep the two lists disjoint and add a test asserting an unauthorized filter is never applied.
  • Relation/dot notation can collide with real columns. A ?author.name= filter and an author column on the parent table are different things; be explicit about which one you mean.
  • Debugging: build the query incrementally and call ->toSql() / ->getBindings() after filter([...]) to see exactly which filters ran. Log the input array in the controller when a filter "disappears" in production but works locally.
  • Config caching: if your app caches configuration, re-clear it after adding model-level filter configuration, and remember that model properties are not part of the config cache.
  • Small ecosystem, low dependents. With 445 stars and effectively no downstream packages, you are the QA. Pin the exact version in composer.json, read the changelog between releases (4.5.6 added Laravel 13.x support; the 2026-02-27 release is recent), and wrap the public surface (filter(), whitelist, ignore hook) behind your own base trait so a breaking rename is a one-file fix.
  • Framework constraint mismatch is the most likely upgrade failure. If composer update refuses to move you to Laravel 13, check that this package is at ^4.5.6 before hunting for a problem in your own code — the framework constraint in an older release is the usual culprit.
  • Extension point: the cleanest way to grow with this package is a FilterableModel base class (or shared trait) that declares the whitelist, ignoreRequest(), and any cross-cutting filters. Everything else stays as plain scope-like methods on individual models.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource