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

Laravel Eloquent Filters Laravel Package

pricecurrent/laravel-eloquent-filters

Laravel Eloquent Filters adds reusable, request-driven filters to Eloquent queries. Define filter classes, map query-string parameters, and compose clean, testable filtering logic for search pages, APIs, and admin lists without bloating controllers or models.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require pricecurrent/laravel-eloquent-filters

No service provider or config file to publish — the package is a thin set of classes. If your app uses package discovery, you're done.

Where to look first

Read these three files before writing anything (the whole package is a few hundred lines):

  1. src/Filterable.php — the trait added to models; exposes the filter() query scope.
  2. src/AbstractFilter.php — the contract every filter implements (isApplicable(), apply(Builder $query): Builder).
  3. src/FiltersCollection.php — the container that lets you apply many filters at once.

First use case — an opt-in search filter on an index endpoint

// app/Models/User.php
use Illuminate\Database\Eloquent\Model;
use Pricecurrent\LaravelEloquentFilters\Filterable;

class User extends Model
{
    use Filterable;
}
// app/Filters/User/NameFilter.php
namespace App\Filters\User;

use Illuminate\Database\Eloquent\Builder;
use Pricecurrent\LaravelEloquentFilters\AbstractFilter;

class NameFilter extends AbstractFilter
{
    public function __construct(private ?string $name) {}

    public function isApplicable(): bool
    {
        return filled($this->name);
    }

    public function apply(Builder $query): Builder
    {
        return $query->where('name', 'like', "%{$this->name}%");
    }
}
// app/Http/Controllers/UserController.php
use App\Filters\User\NameFilter;
use App\Models\User;
use Illuminate\Http\Request;

public function index(Request $request)
{
    $filter = new NameFilter($request->input('name'));

    return User::query()
        ->filter($filter)
        ->paginate();
}

That is the entire onboarding. If you can write an Eloquent scope, you can write a filter in under five minutes.

Implementation Patterns

One filter class per concern. Treat each filter as a named, single-purpose predicate: NameFilter, StatusFilter, CreatedAfterFilter, HasOrdersFilter. Don't stuff an if/elseif chain into a single filter — the whole value of the package is composability.

Gate optional params with isApplicable(). This is the most-used idiom day-to-day. Query strings arrive as empty strings/null; isApplicable() is where you decide "is this filter actually in play?" Return filled($this->value), $this->value !== null, is_array($this->ids) && count($this->ids) > 0, etc. The scope silently skips filters that report false, which keeps controllers clean.

Compose with FiltersCollection for real endpoints. For anything beyond a single filter, build the collection from the request and let the trait iterate:

use Pricecurrent\LaravelEloquentFilters\FiltersCollection;

$filters = new FiltersCollection([
    new NameFilter($request->input('name')),
    new StatusFilter($request->input('status')),
    new CreatedAfterFilter($request->date('created_after')),
]);

$users = User::query()->filter($filters)->paginate();

Wrap that construction in a small per-resource factory class (App\Filters\User\UserIndexFilters::fromRequest($request)) so controllers stay one-liners and the mapping from query params to filters lives in one testable place. This is the pattern most teams converge on within a week.

Cross-model reusable filters. Because filters only receive a Builder, a CreatedAfterFilter or SoftDeletedFilter works across every model using the trait — good for admin tooling and reporting endpoints. Model-specific filters should live under app/Filters/{Model}/.

Compose with existing scopes and relations. Filters are just query mutations, so ->filter($filters)->with('team')->whereHas(...) chains naturally. Keep ordering and eager-loading in the controller/query object, not inside filters.

Test the filter, not the endpoint, for query-shape bugs. Assert isApplicable() returns false for empty input, and use ->toSql() / assertDatabaseCount to verify apply() produces the intended WHERE. One FilterTest per class is cheap insurance against a typo'd column in a rarely-used filter.

Integrate with FormRequests for validation. The package deliberately does not parse or validate input. Validate request params in a FormRequest, then construct filters from $request->validated() — that way a filter never receives a type it can't handle.

Gotchas and Tips

apply() must return the builder. It's easy to write $query->where(...) without return inside a non-arrow method. The scope will quietly apply nothing and your tests will pass because no assertion checks the SQL. Always return $query (or use arrow functions for simple filters).

Skipped filters are silent. When isApplicable() returns false there is no log line or exception — the filter just doesn't run. During debugging, temporarily log inside isApplicable() or dump the collection before ->filter() to confirm what actually fired.

No request-to-filter binding is built in. There's no allowedFilters, no ?filter[name]= parser, no nested-parameter support. You write the constructor arguments and the mapping yourself. This is fine (and arguably the point), but don't expect Spatie-style ergonomics. If your product needs a filter DSL, budget for a small adapter layer on top.

Type juggling of query strings. $request->input('status') is always a string; $request->boolean('active') or filter_var before passing into the filter. isApplicable() using filled() will correctly reject '', but "0" is considered filled — be explicit where zero is a valid value.

Order within the collection matters for SQL, not correctness. Multiple where filters compose in insertion order. Put filters that change SELECT/JOIN behavior (e.g. whereHas) before heavily-selective where clauses only if you care about the generated SQL shape; the package will not reorder for you.

Scope semantics on empty collections. ->filter(new FiltersCollection([])) is a no-op — safe to pass unconditionally rather than branching in the controller. Prefer that over if ($filters->isNotEmpty()).

Small package, small surface. 176 stars, a low score, and one maintainer means: pin the version in composer.json, read src/ when you upgrade (last release March 2025), and don't rely on a documentation site. The extension points you have are the public AbstractFilter and Filterable trait — if you override filter() in your model, watch for trait conflicts with SoftDeletes/HasScopes.

Consider the build-vs-adopt line. The whole feature is effectively one trait with a foreach and an abstract class. If your team already has a HasFilters trait, this package adds little. Its value is convention: a consistent AbstractFilter shape across the codebase, which pays off when ten people are writing filters.

Debugging tip: create a dumpSql($filter) helper in your base test case that builds the query with a single filter and asserts against ->toSql(). It catches the missing-return and wrong-column classes of bugs faster than full feature tests.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
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