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 Query Builder Laravel Package

zing/laravel-query-builder

A Laravel package for building cleaner, reusable Eloquent queries with composable filters, sorting, and relationship helpers—keeping controllers and models concise while making complex database queries easier to organize and maintain.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a maintained fork of spatie/laravel-query-builder (same namespace conventions, same public API), so the fastest way to get productive is to treat the Spatie docs as your reference and verify deltas against this repo's README and src/.

  1. Install

    composer require zing/laravel-query-builder
    php artisan vendor:publish --provider="Zing\QueryBuilder\QueryBuilderServiceProvider" --tag=config
    

    Confirm the service provider is auto-discovered (Laravel 5.5+) — if not, register it in config/app.php.

  2. Where to look first

    • README.md — the fork's documented deltas from Spatie (PHP/Laravel version bumps, added filter types, bug fixes).
    • config/query-builder.php — the contract for parameters (filter, sort, include, fields), count param, and the custom filter class namespace. This is the file you will actually edit in anger.
    • src/QueryBuilder.php — the for(), allowedFilters(), allowedSorts(), allowedIncludes(), allowedFields() surface.
    • tests/ — the cheapest way to confirm behavior that differs from Spatie.
  3. First use case — a paginated index endpoint:

    // app/Http/Controllers/Api/UserController.php
    public function index()
    {
        $users = QueryBuilder::for(User::class)
            ->allowedFilters(['name', 'email'])
            ->allowedSorts(['name', 'created_at'])
            ->allowedIncludes(['posts', 'team'])
            ->defaultSort('-created_at')
            ->paginate(25)
            ->withQueryString();
    
        return UserResource::collection($users);
    }
    

    Hit /api/users?filter[name]=jon&sort=-created_at&include=posts&page=2 and you have the whole happy path. Build this one endpoint end-to-end before touching custom filters — it exercises config, whitelisting, and pagination together.

Implementation Patterns

Centralize the builder in a Form Request or a dedicated method. Controllers that inline allowedFilters() chains become unreadable at ~8 filters. A buildQuery() method on the request, or a QueryBuilder factory on the model, keeps it testable:

// app/Http/Requests/IndexUserRequest.php
public function query(): QueryBuilder
{
    return QueryBuilder::for(User::class)
        ->allowedFilters($this->filters())
        ->allowedSorts(['name', 'created_at'])
        ->allowedIncludes(['posts'])
        ->defaultSort('-created_at');
}

Filter types are the main lever. AllowedFilter::exact(), ::partial(), ::scope(), ::beginsWith(), ::scope(), ::trashed(), and ::custom() cover most needs; ::exact() is the safe default for IDs and enums, ::partial() for free text. Prefer ::scope() over custom filter classes when the constraint is a where clause — the logic stays on the model where it belongs and can be reused outside HTTP.

Custom filters for anything non-trivial. For date ranges, geo-radius, or JSON column queries, implement Zing\QueryBuilder\Filters\Filter (interface signature: __invoke(Builder $query, $value, string $property)), register the namespace in config, and reference it by class:

->allowedFilters([
    AllowedFilter::custom('created_between', new CreatedBetweenFilter),
])

Keep filters single-purpose and throw InvalidQueryException on malformed input so the 400 surfaces automatically.

Sparse fieldsets + includes together. allowedFields(['id', 'name', 'email']) pairs with allowedIncludes() for GraphQL-ish clients, but note that fields and include are separate parameter families — document both in your API contract or clients will silently get full models back.

Chain from an existing builder, not just a class. QueryBuilder::for(User::where('active', true)) lets you layer tenant/authorization scoping before the request-driven clauses. This is the cleanest place to enforce where('team_id', auth()->user()->team_id) — do it here, never inside an allowed filter.

Sorting on relationship columns requires an explicit join; the package sorts columns on the base table only. Either add the join before ->allowedSorts(), or expose a scopeSortByX() and call it from allowedSorts(AllowedSort::custom(...)).

Test the query string, not the internals. $this->getJson('/api/users?filter[name]=x&sort=-id')->assertOk() catches whitelist typos, which are the #1 failure mode. Also assert the negative case (?filter[password]=... is ignored) at least once per endpoint.

Reuse across resources. A QueryBuilder instance can be serialized through pagination into an API Resource; call ->withQueryString() on the paginator so next/prev links survive the filter set.

Gotchas and Tips

  • This is a 4-star fork of a very popular package. Treat it as API-compatible-with-Spatie, not as a drop-in guarantee. Pin the exact version in composer.json and read the changelog before composer update; if you hit a bug or need long-term support, spatie/laravel-query-builder is the upstream and swapping back is usually a one-line namespace change. Decide early which you're standardizing on across the codebase.
  • Everything is allow-listed, and violations are silent. An unlisted filter[foo] is not an error — it's ignored. This makes a typo in allowedFilters() look like a broken client. Log or assert on unknown parameters in non-production if your clients are external.
  • InvalidQueryException is thrown for known-but-invalid values, not unknown ones. Register a handler to return 400 with the offending parameter rather than a 500.
  • Empty-string filters behave differently from missing filters. ?filter[name]= may produce LIKE '%%' (harmless) or an exact match on '' depending on filter type. Normalize in a middleware or use ->removeEmptyFilters() if the fork exposes it.
  • include is the cheapest way to DOS yourself. Unbounded nested includes (include=posts.comments.author) generate deep eager loads. Constrain with closures on the relationship or cap depth; allowedIncludes accepts constrained builders for exactly this.
  • Config caching bites. If config/query-builder.php is cached, changing the custom filter namespace or parameter names won't take effect until php artisan config:clear. This is the most common "it works locally" report.
  • Sorting on a column not in the select list silently fails or throws a SQL error depending on driver. Always include the sort column in allowedFields/select when using sparse fieldsets.
  • Do not pass raw request input into allowedFilters(). Build the allow-list from a constant or config, never from $request->all(). The package's security model assumes a developer-authored whitelist.
  • Debugging tip: ->toSql() after the chain, plus ->getBindings(), is faster than reading the source. If a filter isn't applying, dump both — it's almost always a whitelist mismatch or an empty-value short-circuit.
  • Extension points worth knowing: custom Filter classes, a subclassed QueryBuilder (override for() to inject a default scope), and the published config's parameters map (rename filter/sort/include to match an existing API contract you can't change).
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.
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
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle