Skip to content

Filters and Clauses

A Filter describes the input users interact with. A Clause describes the comparison to perform. Keeping those responsibilities separate lets one Text Filter offer “contains,” “starts with,” and “equals” without duplicating the input definition.

Automatic Filters are enabled by default and are created for Columns marked filterable:

InertiaX::table('users')
->data(User::class)
->autoColumns()
->filterableColumns()
->autoFilters();

The inferred Column type selects the matching Text, Number, Boolean, Date, DateTime, or Time Filter. This is a useful starting point. Use filterable() on individual Columns when filtering should be more selective, and declare an explicit Filter when labels, options, or comparisons are domain policy.

Filter Typical values Built-in comparisons
TextFilter strings equals, contains, starts/ends with, negations, set/unset
NumberFilter numbers equals, comparisons, between, set/unset
BooleanFilter booleans boolean equality and set state
SelectFilter one or many options selection equality and set state
DateFilter dates equality, before/after, ranges, set/unset
DateTimeFilter timestamps date-time comparisons and ranges
TimeFilter times time comparisons and ranges

Declare explicit Filters in a reusable Table:

use InertiaX\Components\Table\Filters\BooleanFilter;
use InertiaX\Components\Table\Filters\NumberFilter;
use InertiaX\Components\Table\Filters\SelectFilter;
use InertiaX\Components\Table\Filters\TextFilter;
protected function filters(): array
{
return [
TextFilter::make('name', 'Name'),
NumberFilter::make('score', 'Score'),
BooleanFilter::make('active', 'Active'),
SelectFilter::make('status', 'Status')->options([
'active' => 'Active',
'invited' => 'Invited',
]),
];
}

Built-in Clauses execute against both Eloquent and Collection sources with equivalent meaning. Date, datetime, and time Filters accept the exact values emitted by native browser controls: YYYY-MM-DD, local YYYY-MM-DDTHH:mm[:ss[.SSS]], and HH:mm[:ss[.SSS]]. Laravel normalizes those values before validation and Eloquent/Collection comparison; malformed or impossible dates fail instead of being guessed.

SelectFilter::make('role', 'Role')
->options([
'admin' => 'Administrator',
'engineer' => 'Engineer',
])
->multiple();

Options can be deferred with a closure. If the callback is source-specific, declare the supported source kinds through options(..., DataSourceKind::Eloquent) or supportsOptionSources(...). autoOptions(limit: 200) derives bounded distinct values from the source; use explicit options when labels or allowed values are domain policy.

When automatic option discovery reaches its limit, the protocol marks the options as truncated and the built-in UI displays an accessible notice. Omitted values are not silently accepted as select options; increase the deliberate bound or provide explicit domain options when the complete set must be selectable.

Filters use the same explicit collection mutations as Tables:

TextFilter::make('name', 'Name')
->removeClause('not_contains')
->defaultClause('contains');

Available operations are addClause(), replaceClause(), removeClause(), clearClauses(), setClauses(), orderClauses(), and defaultClause(). setClauses() means clear then add.

Custom Clause identifiers must be namespaced:

use Illuminate\Database\Eloquent\Builder;
use InertiaX\Components\Table\Enums\DataSourceKind;
use InertiaX\Components\Table\Filters\Clauses\Clause;
TextFilter::make('role', 'Role')
->clearClauses()
->addClause(
Clause::make('acme/role-equals', 'Has role')
->validateUsing(
fn (mixed $value): bool => is_string($value)
&& in_array($value, ['Admin', 'Engineer'], true),
)
->applyUsing(
fn (Builder $query, string $column, string $value): Builder =>
$query->where($column, $value),
DataSourceKind::Eloquent,
),
);

validateUsing() validates incoming values. modifyUsing() normalizes a valid value before the comparison. applyUsing() owns the source operation, and the listed source kinds are mandatory.

If the Clause also supports Collections, provide behavior that is correct for a Collection and declare DataSourceKind::Collection. Do not claim parity by passing the wrong source type into an Eloquent callback.

Most customization belongs to Clauses because they own comparison behavior. Use applyClauseUsing() only when the Filter must route every selected Clause through shared source-specific behavior:

$filter->applyClauseUsing(
function ($source, string $column, mixed $value, Closure $applyClause) {
// Cross-cutting Filter behavior, then delegate to the selected Clause.
return $applyClause($source, $column, $value);
},
DataSourceKind::Eloquent,
);

This is a localized escape hatch, not the normal way to define a comparison. The selected Clause still owns validation, normalization, capability declarations, and its comparison semantics.

The default FilterMode::Basic presents the bounded built-in filter experience. Switch the Table to FilterMode::Advanced when users need recursive groups and explicit Clause selection:

UsersTable::make('users')->filterMode(FilterMode::Advanced)

The exact backed strings are accepted too: filterMode('advanced') is equivalent to the enum call. Invalid strings fail immediately, while protected class defaults remain enum-typed.

Basic mode accepts only one root and group with direct conditions. Nested groups and or are advanced-mode semantics and are rejected even when state is submitted without the built-in UI.

The server validates all incoming filter state against the emitted definitions before applying it. Advanced filter state is bounded to eight group levels including the root and 100 total condition or child-group nodes, so invalid or excessive input fails before query/Collection execution.