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

Query Filter Laravel Laravel Package

liqrgv/query-filter-laravel

A Laravel package for building expressive, reusable query filters. Apply request-based filtering, sorting, and search to Eloquent queries with clean, composable filter classes—ideal for APIs and admin lists.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and confirm the package's actual surface before building on it — it's a small (10-star) package, so pin the version and read src/ once.

composer require liqrgv/query-filter-laravel
  1. Check composer.json first for the supported PHP/Laravel range. Small packages often lag a major Laravel release; confirm it matches your app before committing.
  2. Publish config if offered (tag name varies — check the README or php artisan vendor:publish interactive list). Skip if the package is config-free.
  3. Register the package's entry point — typically a trait on the model you want filterable, or a base QueryFilter/RequestFilter class you extend per resource.
  4. Whitelist filters explicitly. The core idea is "Request → Query Builder": you hand it the request and a model query, and it maps ?name=jo&status=active onto where clauses. Never let it accept arbitrary column names.

First use case — a filtered index endpoint:

// routes/api.php
Route::get('/users', [UserController::class, 'index']);

// app/Http/Controllers/UserController.php
public function index(Request $request)
{
    return User::query()
        ->filter($request)      // or ->applyRequest($request) — verify exact name
        ->paginate(25);
}

Start with 2–3 scalar filters on one model, assert the SQL with ->toSql() in a test, then expand. Don't wire it into every controller before you've verified behavior on one.

Implementation Patterns

Per-resource filter classes (preferred). Define one class per model/resource that declares allowed params and how each maps to the builder. This keeps controllers thin, makes filters unit-testable, and gives you a single place to audit for injection and N+1 issues.

class UserFilter
{
    protected array $allowed = ['name', 'status', 'created_after', 'sort'];

    public function name($q, $v)    { $q->where('name', 'like', "%{$v}%"); }
    public function status($q, $v)  { $q->where('status', $v); }
    public function createdAfter($q, $v) { $q->where('created_at', '>=', $v); }
}

Layer your own wrapper. Even if the package exposes a trait, create a thin app-level abstraction (App\Support\FilterableQuery or a base filter class) so swapping packages later is a one-file change. This is the single most valuable habit with a low-adoption dependency.

Validate before filtering. Put query params through a FormRequest (or $request->validate([...])) so date formats, enums, and page sizes are sane before they reach the builder. Validation also documents the API in one place.

Combine with eager loading deliberately. Filters that touch relations usually compile to whereHas/whereRelation. Apply filters before with() so relation existence checks and eager loads don't fight each other, and consider whereRelation(...)->with(...) only when you actually need the relation data.

Sorting and pagination as first-class params. Treat ?sort=-created_at and ?per_page= as part of the same filter contract. Map sort keys through an allowlist (['name', 'created_at']) rather than passing column names through.

Compose filters across endpoints. For admin vs. public listings, either subclass the resource filter (disable fields) or pass an allowed-fields array to the constructor. Avoid branching inside the filter with auth() checks — pass explicit context in.

Test the SQL, not just the response. A filter test should assert the generated query for a given input:

$sql = User::query()->filter($request)->toSql();
$this->assertStringContainsString('"status" = ?', $sql);

Gotchas and Tips

  • Unvalidated column names are the main risk. If the package maps request keys straight to where/orderBy without an allowlist, you have a SQL-injection-adjacent surface and unpredictable behavior on typos. Always restrict to declared filters.
  • Empty strings and null. ?name= frequently arrives as "", not missing. Decide per filter whether empty means "ignore" or "search for empty", and encode that in the filter method. Silent LIKE '%%' full-table scans are a common production surprise.
  • Operator injection. Beware params like ?status[gt]=active or ?sort=name;drop. If the package accepts arrays, validate the shape (string|in:..., array with max: keys) and reject operator-style input.
  • N+1 from relation filters. A whereHas for a list is fine on count but can duplicate or multiply rows if you also join. Prefer whereRelation / subqueries and confirm with DB::listen or Telescope under a seeded dataset.
  • Date/time parsing. ?created_after=2024-13-01 should fail validation, not silently become 1970. Normalize to your app timezone and cast explicitly in the filter.
  • Paginated + filtered totals. Some implementations mutate the builder in a way that breaks count() (e.g., groupBy added by a filter). Verify paginate()->total() on every filter you ship.
  • Debugging: log $query->toSql() and getBindings() in a local middleware, or enable DB::listen for the endpoint. When a filter "does nothing", it's almost always a name/allowlist mismatch rather than a bug in the package.
  • Config quirks: if a config publishes, commit it. Defaults can change between minor versions on a package this small, and a published file protects you.
  • Extension points: favor the package's filter class/hook over forking. If there's no hook for something you need (e.g., a custom OR grouping), implement it as a subclass in your app rather than editing vendor/.
  • Upgrade discipline: with ~10 stars, expect occasional breaking changes between minors. Pin with "^x.y" and read the diff before bumping; keep your app-level wrapper so a swap is cheap.
  • Don't use it for everything. If a query is genuinely bespoke (reporting, complex aggregations), hand-build it. Shoehorning a reporting endpoint through generic request filtering tends to produce unmaintainable filters.
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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib