spatie/laravel-query-builder
Build safe, flexible Eloquent queries from incoming API requests. Supports whitelisted filtering (partial/exact/scope/custom), sorting, includes, field selection, pagination, and grouped AND/OR filters—ideal for JSON:API-style endpoints with minimal boilerplate.
?filter[name]=John, ?sort=-created_at).AllowedFilter, AllowedSort, and AllowedInclude, enabling fine-tuned permissions per endpoint or model.withTrashed(), whereHas()), reducing duplication.Model::query() with QueryBuilder::for(Model::class) and configure allowed parameters.assertDatabaseCount() with dynamic queries).with() or eager loading.allowedIncludes() and with() to preload relationships.allowedFilters/allowedSorts could expose sensitive fields (e.g., password).->allowedFilters(['name', 'email'])).Sort, Filter).composer.json and monitor UPGRADING.md.QueryBuilder??sort=password) be logged/handled in production?AllowedFilter::scout()).laravel-permission for role-based filtering./api/public/users).User::query() with QueryBuilder::for(User::class).// Before
return User::where('active', true)->get();
// After
return QueryBuilder::for(User::class)
->allowedFilters(['name', 'role'])
->allowedSorts('name', 'created_at')
->get();
filter, then sort).QueryBuilder per endpoint.public function handle(Request $request, Closure $next) {
if ($request->routeIs('admin.*')) {
$request->merge(['filter[active]' => true]);
}
return $next($request);
}
# OpenAPI
/users:
get:
parameters:
- name: filter[name]
in: query
description: Filter by name (partial match)
required: false
schema:
type: string
LENGTH()) may vary by DB (test with your RDBMS).Cache::remember()).QueryBuilder::cacheFor() for static queries.allowedFilters/allowedSorts for 2–3 core endpoints.allowedIncludes for nested data (e.g., /users?include=posts.comments).AllowedFilter::scout()).InvalidSortQuery).allowedFilters/allowedSorts may diverge across endpoints.// app/Traits/Queryable.php
trait Queryable {
protected function applyQueryBuilder() {
return QueryBuilder::for($this->model)
->allowedFilters($this->filters)
->allowedSorts($this->sorts);
}
}
QueryBuilder::toSql() to inspect generated queries:
$query = QueryBuilder::for(User::class)->allowedFilters(['name']);
\Log::debug($query->toSql(), $query->getBindings());
?sort=password).try {
return $query->get();
} catch (InvalidSortQuery $e) {
return response()->json(['error' => 'Invalid sort: ' . $e->getMessage()], 400);
}
LIKE queries or full-text search may slow down under load.
ALTER TABLE users ADD FULLTEXT(name)).ORDER BY on non-indexed columns.
BRIN indexes).posts.comments.author) can bloat queries.
with() to preload critical paths.return Cache::remember("users_{$request->filter}", now()->addHours(1), function () use ($request) {
return QueryBuilder::for
How can I help you explore Laravel packages today?