Custom filters
A filter is a field; its clauses are the comparisons users choose for it. Each clause takes no
value, one value, a list or a range. Add clauses to a built-in filter, or start from a blank
Filter.
Add a custom comparison
Section titled “Add a custom comparison”This date filter also offers “In the last … days”, which takes a number of days:
Laravel
use Illuminate\Database\Eloquent\Builder;use InertiaX\Table\Enums\DataSourceKind;use InertiaX\Table\Filters\Clauses\Clause;use InertiaX\Table\Filters\DateFilter;
DateFilter::make('created_at', 'Created') ->addClause( Clause::make('acme/last-days', 'In the last … days') ->valueType('number') ->validateUsing(fn (mixed $value): bool => is_int($value) && $value > 0) ->applyUsing( fn (Builder $query, string $column, int $value): Builder => $query->where($column, '>=', now()->subDays($value)), DataSourceKind::Eloquent, ), );Custom clause keys are namespaced, such as acme/last-days. valueType('number') gives the clause
a number input; without it, the clause would take a date, like the filter’s other clauses.
validateUsing() rejects fractions and negative numbers before the int callback runs.
applyUsing() receives the filter’s column and the validated value, and runs only on the source
kinds you declare.
A clause can also take no value:
Laravel
use InertiaX\Table\Filters\TextFilter;
TextFilter::make('email') ->addClause( Clause::make('acme/work-email', 'Is a work email') ->withoutValue() ->applyUsing( fn (Builder $query, string $column): Builder => $query->where($column, 'not like', '%@gmail.com'), DataSourceKind::Eloquent, ), );range() takes a [from, to] pair and multiple() a list of select values. See
Clause API for every input and value type.
Build a filter from scratch
Section titled “Build a filter from scratch”Filter::make() starts a filter with no comparisons. This one finds users who belong to an
organization:
Laravel
use InertiaX\Table\Filters\Filter;
Filter::make('is_org', 'Organization') ->addClause( Clause::make('acme/org', 'Is an organization') ->withoutValue() ->applyUsing( fn (Builder $query): Builder => $query->whereNotNull('company_id'), DataSourceKind::Eloquent, ), );To reuse it, extend Filter and declare its key and clauses:
Laravel
namespace App\Tables\Filters;
use Illuminate\Database\Eloquent\Builder;use InertiaX\Table\Enums\DataSourceKind;use InertiaX\Table\Filters\Clauses\Clause;use InertiaX\Table\Filters\Filter;
class OrganizationFilter extends Filter{ protected string $key = 'is_org';
protected ?string $label = 'Organization';
public function clauses(): array { return [ Clause::make('acme/org', 'Is an organization') ->withoutValue() ->applyUsing( fn (Builder $query): Builder => $query->whereNotNull('company_id'), DataSourceKind::Eloquent, ), ]; }}List OrganizationFilter::make() in a table’s filters(); OrganizationFilter::make('organization')
gives it another key. A filter without a key, or without clauses, fails when the table is prepared,
with an error that says what to add.
Replace the input
Section titled “Replace the input”Give a clause a custom value type, then register an input for that value type in React. This filter takes a role through a custom control:
Laravel
Filter::make('role', 'Role') ->addClause( Clause::make('acme/has-role', 'Has role') ->valueType('acme/role') ->validateUsing( fn (mixed $value): bool => in_array($value, ['admin', 'engineer'], true), ) ->applyUsing( fn (Builder $query, string $column, string $value): Builder => $query->where($column, $value), DataSourceKind::Eloquent, ), );A custom value type accepts any value except null, so validate the values you expect.
React
import { register, type TableCustomizationOptions, type TableFilterInputRendererProps,} from '@inertiax/react-table';import { AppTable } from '@/components/app-table';
function RoleInput({ ariaLabel, id, value, onChange }: TableFilterInputRendererProps) { return ( <select aria-label={ariaLabel} id={id} value={typeof value === 'string' ? value : ''} onChange={(event) => onChange(event.target.value || undefined)} > <option value="">Any role</option> <option value="admin">Administrator</option> <option value="engineer">Engineer</option> </select> );}
const customization = { filterInputs: [register('acme/role', RoleInput)],} satisfies TableCustomizationOptions;
export function UsersIndex() { return <AppTable prop="users" customization={customization} />;}Emit undefined to clear the draft value. The built-in editor commits with Add filter,
Save, or Apply filters. Users remove applied conditions with the remove button.
Preserve the server’s value types: string "1", number 1, and Boolean true are distinct.
The input also receives the filter definition and the selected clause. clause.input.valueType
names the value type, and a select input lists its choices in clause.input.options. The input
mounts while a condition is edited.
Format applied values
Section titled “Format applied values”Add formatFilterValue to show a readable value in Basic filter chips:
React
const customizationWithLabels = { ...customization, formatFilterValue: ({ clause, value }) => { if (clause.input?.valueType !== 'acme/role') return undefined; if (value === 'admin') return 'Administrator'; if (value === 'engineer') return 'Engineer'; return undefined; },} satisfies TableCustomizationOptions;
<AppTable prop="users" customization={customizationWithLabels} />;Return a string to change the label or undefined to use the next formatter. The submitted value
stays unchanged. See Filter value summaries for
fallbacks and callback precedence.
Wrap clause application
Section titled “Wrap clause application”To query another column, use column(); see
Declare a filter. Use applyClauseUsing() when every
comparison of a filter needs the same wrapper, such as a has-many relation:
Laravel
TextFilter::make('tag', 'Tag') ->setClauses(['equals', 'contains']) ->applyClauseUsing( fn (Builder $query, mixed $value, \Closure $applyClause): Builder => $query->whereHas( 'tags', fn (Builder $tags) => $applyClause($tags, 'name', $value), ), DataSourceKind::Eloquent, );The model needs a tags() relation whose records have a name field. The selected clause still
validates the value and performs the comparison, here on each tag’s name. Inside whereHas(), a
negated comparison would match records with any other tag, so this filter offers only positive
ones. See Filter API for callback context.
Keep a select filter in the toolbar
Section titled “Keep a select filter in the toolbar”For a stock status dropdown, call pinned(); see
Filtering.