Laravel Query Builder
Turn API request query strings into safe, allow-listed Eloquent filters, sorts, and includes
Repository Health
Technical Analysis
Laravel Query Builder is a Spatie package that translates the query-string conventions of a JSON:API-style request (filter[name]=John, sort=-created_at, include=posts, fields[users]=id,email) directly into an Eloquent query. Instead of hand-rolling conditional if statements for every filterable or sortable column in a controller, developers declare an allow-list of filters, sorts, includes, and fields on a QueryBuilder::for() call, and the package parses the incoming request and applies matching clauses automatically.
It is built specifically for API endpoints where clients need flexible, self-service querying without exposing the full Eloquent query surface to untrusted input. Every filter, sort, and include must be explicitly allowed, so unexpected or malicious query parameters are rejected with a typed exception rather than silently reaching the database layer.
What You Get
- A
QueryBuilder::for(Model::class)entry point that behaves like a normal Eloquent builder via__callforwarding, so any existing builder method still works AllowedFiltertypes for exact, partial, scope-based, callback-based, begins-with/ends-with, operator, and grouped OR/AND filtersAllowedIncludesupport for eager-loading relationships, relationship counts, sums, averages, min/max, and existence checks straight from?include=AllowedSortsupport for field-based and custom callback-based sorting via?sort=, including descending order with a-prefix- Sparse fieldset support via
?fields[table]=col1,col2to limit which columns are selected per included model - Typed exceptions (
InvalidFilterQuery,InvalidSortQuery,InvalidIncludeQuery,InvalidFieldQuery, etc.) thrown when a request references something not on the allow-list
Common Use Cases
- Building a public or partner-facing REST API where consumers need to filter, sort, and include related resources without the backend team adding a new endpoint for every combination
- Replacing repetitive
if ($request->has('filter'))conditionals scattered across resource controllers with a single declarative allow-list - Enforcing that only vetted columns and relationships can be queried by API clients, closing off accidental data exposure or N+1-inducing arbitrary includes
- Adding JSON:API-style query conventions to an existing Laravel API without adopting a full JSON:API package
Under The Hood
Architecture - The package centers on QueryBuilder (src/QueryBuilder.php), which wraps an injected Eloquent Builder or Relation and composes four traits — FiltersQuery, AddsIncludesToQuery, SortsQuery, and AddsFieldsToQuery — each responsible for one query concern. QueryBuilder::for() is the entry point: it resolves the current request into a QueryBuilderRequest (a Illuminate\Http\Request subclass that parses filter[], sort, include, and fields[] query parameters, including delimiter-aware array parsing), then each trait’s allowedX() method cross-references the parsed request against a developer-declared allow-list before mutating the underlying builder. Unmatched calls fall through to the wrapped Eloquent builder via ForwardsCalls::forwardCallTo() in __call, and ArrayAccess/__get/__set are implemented so the object is interchangeable with a real builder in most contexts.
Tech Stack - Requires PHP ^8.3 and Laravel (illuminate/database, illuminate/http, illuminate/support) ^12.0|^13.0, registered via spatie/laravel-package-tools. Dev tooling uses Pest ^4.0 and PHPUnit ^12.0 for tests, Larastan (PHPStan for Laravel) ^3.0 with a checked-in baseline for static analysis, Orchestra Testbench ^10.0|^11.0 to run tests against a real Laravel app skeleton, and Mockery for mocking — a conventional, current Spatie/Laravel package stack with no exotic dependencies.
Code Quality - The tests/ directory contains 24 PHP test files covering filters, includes, sorts, field selection, request parsing, and the public QueryBuilder API, run via composer test (Pest). A phpstan-baseline.neon and phpstan.neon.dist show static analysis is enforced in CI with a tracked baseline rather than being aspirational. Source classes are narrowly scoped (one filter type per class under src/Filters/, one include type per class under src/Includes/), use PHP 8 constructor property promotion, and are annotated with generic PHPDoc templates (@template TModel of Model) for IDE support despite PHP’s lack of native generics — a sign of deliberate API ergonomics investment rather than just functional correctness.
API Design - The public surface is a single static entry point, QueryBuilder::for($subject, $request), that mimics Eloquent’s own fluent builder style (->allowedFilters()->allowedIncludes()->allowedSorts()->get()), so Laravel developers can adopt it with near-zero new mental model. Because unknown method calls forward to the wrapped builder, existing Eloquent knowledge (where(), withTrashed(), custom scopes) keeps working unchanged inside the chain. Extensive docs (docs/features/, docs/advanced-usage/) cover filtering, sorting, includes, and field selection with runnable examples, and typed exceptions per failure mode make it straightforward to map invalid requests to specific HTTP error responses.