ComfyCoders TranquilTools
Docs /Laravel Vue TableBuilder
Repository
v1

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 LIKE and LIKE BINARY
  • PostgreSQL: toggles between ILIKE and LIKE

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:

  1. Callback/select filters applied (applyFilters)
  2. Search inputs applied (applySearchInputs)
  3. Sorting + eager loading applied (applySortingAndEagerLoading)
  4. Default sort applied if no sort query param
  5. 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);