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 Filters Laravel Package

midsonlajeanty/laravel-filters

A Laravel package for adding reusable, request-driven filters to Eloquent models. Define filter classes and let incoming query parameters refine your database queries cleanly, keeping controllers and models lean.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package via Composer and, on first install, publish its config if you plan to customise the default namespace or filter path:

composer require midsonlajeanty/laravel-filters
php artisan vendor:publish --tag=laravel-filters

Two things to read first:

  1. The trait — how a model opts into filtering (typically a Filterable trait plus a $filters property or filter() scope).
  2. The filter class contract — which methods are treated as filters vs. helpers, and what the method signature receives (query builder, value, request).

Minimal first use case — a search endpoint on User:

// app/Filters/UserFilter.php
class UserFilter extends Filter
{
    public function name($value)
    {
        return $this->builder->where('name', 'like', "%{$value}%");
    }

    public function status($value)
    {
        return $this->builder->where('status', $value);
    }
}
// User model
use Filterable;

protected $filters = UserFilter::class;
// Controller
User::filter($request->only(['name', 'status']))->paginate();

That is the whole loop: define methods named after query params, wire the trait to the model, pass a whitelisted array from the request.

Implementation Patterns

One filter class per model, methods named after query params. This is the dominant pattern and the one to standardise on early. The filter class becomes the single place where you audit "what can a client filter on", which is exactly where you want that knowledge to live.

Always pass a whitelist, not $request->all(). Whether you use $request->only([...]), a form request's validated(), or the package's own allowed-filters config, the boundary should be explicit. A filter class only defines what a filter does, not which filters are permitted on a given endpoint — that distinction matters when the same model is exposed on an admin and a public route.

Chain with existing query logic, don't replace it. Filters compose naturally with scopes, with(), and sorting before the terminal call:

User::query()
    ->with('team')
    ->active()                    // your own scope
    ->filter($request->validated())
    ->sortBy($request->query('sort'))
    ->paginate(25);

Relationship filters. For a filter on a related model, prefer whereHas inside the filter method rather than a join, so you keep the model's hydration and any global scopes intact:

public function team($value)
{
    return $this->builder->whereHas('team', fn ($q) => $q->where('slug', $value));
}

Default / fallback behaviour. If the package exposes a default method or a catch-all hook, use it for sensible defaults (e.g. status = 'published' for public listings). Otherwise, keep defaults in the controller so the filter class stays free of presentation decisions.

Range and date filters. Split into paired methods (price_from / price_to) rather than one magic value, so validation and query-string documentation stay obvious.

Testing. Filters are cheap to test with assertDatabaseHas-free query assertions — build the filter against a request array and assert on toSql() bindings or on the returned collection. This is the fastest place to catch a renamed column before it hits production.

Gotchas and Tips

Semver discipline matters here. The package is pre-1.0, so minor bumps (0.1 → 0.2) can legitimately contain breaking changes. The v0.1.2 release is a patch bump with no changelog detail beyond the compare link — treat it as a bug-fix release and safe to adopt, but do not infer compatibility guarantees from it. Pin the constraint to ^0.1.2 and read the compare diff before taking any 0.2.x minor.

Method names are the public API. Renaming a filter method silently changes the accepted query string. Either keep query params snake_case and add explicit aliases, or accept camelCase params — but be consistent, because a mismatch fails quietly (unknown filters are ignored) rather than erroring.

Silent ignores hide typos. A misspelled param (statuss=active) usually falls through as "no filter applied", which looks like a data problem in the UI. During development, log or assert on any request keys that were passed but not consumed by the filter class — it turns a class of subtle bugs into a loud one.

Whitelisting is a security control, not a convenience. Every filter method is raw query-builder access. Passing unfiltered request data into filter() and having a permissive catch-all method is a straight path to unintended LIKE scans or column enumeration. Never accept a column name directly from the request.

Ordering of filters changes results when they overlap. Two filters touching the same column (where then orWhere) compose in method-definition order, not request order. If behaviour depends on ordering, combine them into a single method.

Watch for N+1 on relation filters. whereHas is fine, but loading the relation afterwards needs with() — filtering and eager-loading are separate concerns and the package will not add the latter for you.

Debugging tip. Add a temporary dd($this->builder->toSql(), $this->builder->getBindings()) inside a single filter method to confirm it is firing at all. Nine times out of ten, "the filter isn't working" is either a name mismatch or the trait/$filters wiring missing on that model.

Extension points to reach for first (before forking): the base filter class for shared helpers (range parsing, boolean coercion), a Sortable-style companion trait for ?sort=, and a form request per endpoint for validation. Pushing validation into the filter class couples it to HTTP and makes it awkward to reuse from console commands or queued jobs — keep filter classes request-agnostic and pass plain arrays.

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
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager