Query Builder
QueryBuilder extends TableBuilder and is returned automatically when you pass an Eloquent query builder or relation
to TableBuilder::for(). It handles all database operations: filtering, searching, sorting, and pagination.
How It Is Created
// These all return a QueryBuilder instance:
TableBuilder::for(User::query())
TableBuilder::for(User::class)
TableBuilder::for($user->orders())
TableBuilder::for(new User)
Pagination Methods
Must call one of these to execute the query:
->paginate(15) // LengthAwarePaginator - shows total count
->simplePaginate(15) // SimplePaginator - Previous/Next only
->cursorPaginate(15) // CursorPaginator - efficient for huge tables
->noPagination() // Fetch all results with get()
Omitting perPage uses default_per_page from the config (25 out of the box). A ?perPage= value from the query
string wins, but only when it is one of the configured perPageOptions.
Case Sensitivity
->ignoreCase(true) // case-insensitive search (default)
->ignoreCase(false) // case-sensitive search
- MySQL: toggles between
LIKEandLIKE BINARY - PostgreSQL: toggles between
ILIKEandLIKE
Term Parsing
By default, search terms are split on spaces and combined with OR: john doe returns rows matching john OR doe.
Quoted phrases in the input are kept together.
->parseTerms(true) // split on spaces (default)
->parseTerms(false) // treat entire input as one term
Eager Loading
Columns using dot notation for relationships are eager-loaded automatically:
->column('company.name', 'Company')
// Adds: $query->with('company')
Filter Application Order
When loadResource() is called:
- Callback/select filters applied (
applyFilters) - Search inputs applied (
applySearchInputs) - Sorting + eager loading applied (
applySortingAndEagerLoading) - Default sort applied if no sort query param
- Pagination executed
The same steps without pagination are available as filteredQuery(?Request $request = null), which returns a clone
of the query for custom exports and reports. See Export. Totals use it too.
Query builder resources are filtered by the query only, paginated or not. The in-memory collection filter applies to
array and Collection resources. See Search.
Spatie QueryBuilder Support
You can pass a Spatie QueryBuilder instance. In that case the package skips its own filter/search/sort application and lets Spatie handle those, but still manages pagination and the Vue frontend:
use Spatie\QueryBuilder\QueryBuilder as SpatieQueryBuilder;
$query = SpatieQueryBuilder::for(User::class)
->allowedFilters(['name', 'email'])
->allowedSorts(['name', 'created_at']);
TableBuilder::for($query)->paginate(15);
Spatie applies its filters and sorts while the query is built. When a table class passes a request of its own to
filteredQuery($request), the package builds for() again with that request in the container, so the Spatie query
reads its sort and filters from it. Bulk actions over "all results" and the export get the posted table state this way.
A bulk action on explicitly checked ids builds for() again without the filter parameters, so those ids are never
narrowed by the filters of the page. A table made with TableBuilder::for($query) outside a table class has no for()
to call again and keeps the filters of the page request.
The documented return type of AbstractTable::for() includes the Spatie QueryBuilder and a DB::table() query, so a
table class may declare public function for(): QueryBuilder without a static analysis error. Spatie stays optional:
the type is only in the docblock.
Bulk Action Processing
When a bulk action targets "all results", the query re-applies all active filters and search inputs before chunking:
->bulkAction('Export', each: fn($user) => $user->export())
Rows are processed in chunks of 1,000 via chunkById to handle large datasets safely.
Conditionable
QueryBuilder (like TableBuilder) uses Laravel's Conditionable trait, so you can use when() and unless():
TableBuilder::for(User::query())
->when($request->user()->isAdmin(), fn($table) => $table->column('secret_field', 'Secret'))
->column('name', 'Name')
->paginate(15);
