# Filter API

> Filter builders, keys and columns, empty values, select options, and grouping limits.

Source: https://docs.inertiax.dev/table/reference/filter/

A filter is a field users filter by: a key, a label, its clauses, the default clause, and whether it is pinned. On the server it also knows the column its clauses query. Classes live in `InertiaX\Table\Filters` and start with `FilterClass::make(?string $key = null, ?string $label = null)`.

## Built-in filters

`Filter` starts blank. The others are presets: default clauses plus the value type they use.

| Filter           | Value type | Default clauses, in order                                                                                                    |
| ---------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `Filter`         | `text`     | None                                                                                                                         |
| `TextFilter`     | `text`     | `equals`, `not-equals`, `starts-with`, `not-starts-with`, `ends-with`, `not-ends-with`, `contains`, `not-contains`           |
| `NumberFilter`   | `number`   | `equals`, `not-equals`, `greater-than`, `greater-than-or-equal`, `less-than`, `less-than-or-equal`, `between`, `not-between` |
| `BooleanFilter`  | `boolean`  | `equals`, `not-equals`                                                                                                       |
| `SelectFilter`   | `select`   | `equals`, `not-equals`                                                                                                       |
| `DateFilter`     | `date`     | `equals`, `not-equals`, `between`, `not-between`, `before`, `after`                                                          |
| `DateTimeFilter` | `datetime` | Same as date                                                                                                                 |
| `TimeFilter`     | `time`     | Same as date                                                                                                                 |

A clause without a value type of its own takes its filter’s. See [Value types](https://docs.inertiax.dev/table/reference/clause/#value-types) for the values each one accepts. Built-in clauses support Eloquent and collections.

A filter needs a key and at least one clause. A keyless filter attached to a column takes the column’s key; anywhere else, preparing the table fails with `Filter [InertiaX\Table\Filters\TextFilter] has no key: pass one to make(), or attach the filter to a column with ->filter().` A filter without clauses fails with a `FilterDefinitionException` whose message ends with `the filter has no clauses; add one with addClause().`

## Common methods

| Method                                                                     | Effect                                                                                                                                              |
| -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `key(string $key)`                                                         | The filter’s name in filter state and URLs                                                                                                          |
| `label(?string $label)`                                                    | Label; derived from the key if omitted                                                                                                              |
| `valueType(ValueType\|string $type)`                                       | Value type of the clauses that declare none: a built-in value type or `vendor/name`                                                                 |
| `column(string $column)`                                                   | Column the clauses query; defaults to the key and is never sent to the browser                                                                      |
| `nullable(bool $nullable = true)`                                          | Add `is-set` (**Is not empty**) and `is-not-set` (**Is empty**) after the other clauses, unless they are already there                              |
| `pinned(bool $pinned = true)`                                              | Show the filter in the toolbar unless the page lists its own; see [Pinned filters](https://docs.inertiax.dev/table/reference/react/#pinned-filters) |
| `defaultClause($clause)`                                                   | Choose a registered clause; otherwise the first clause is used                                                                                      |
| `addClause($clause)` / `addClauses(array)`                                 | Add new comparisons                                                                                                                                 |
| `replaceClause($clause)` / `replaceClauses(array)`                         | Replace existing comparisons                                                                                                                        |
| `removeClause($clause)` / `removeClauses(array)`                           | Remove existing comparisons                                                                                                                         |
| `clearClauses()` / `setClauses(array)`                                     | Clear, or clear then add                                                                                                                            |
| `orderClauses(Closure $callback)`                                          | Reorder the clause collection                                                                                                                       |
| `applyClauseUsing(Closure $callback, DataSourceKind\|string $source, ...)` | Wrap clause application; see [Shared clause routing](https://docs.inertiax.dev/table/reference/filter/#shared-clause-routing)                       |

`ValueType` is in `InertiaX\Table\Filters\Enums`. Clause arguments can be clause objects, `ClauseType` enum values, or keys. Duplicate adds and missing replace/remove targets fail. The explicit default must exist in the resulting set, which includes the clauses `nullable()` adds. See [Clause API](https://docs.inertiax.dev/table/reference/clause/).

### Filter classes

Extend `Filter` to reuse a filter. The class can set `protected string $key`, `protected ?string $label`, `protected ?string $column`, `protected bool $pinned` and `protected string $valueType`, and return its clauses from `clauses()`; `make()` arguments and builder methods override them. A table’s `filters()` can list `OrganizationFilter::make()` or `OrganizationFilter::class`. See [Build a filter from scratch](https://docs.inertiax.dev/table/guide/custom-filters/#build-a-filter-from-scratch).

## Automatic and attached filters

Automatic filters require `autoFilters()` and a filterable column. Number, Boolean, datetime, time, and date column classes map to their matching filters; other columns map to text. When an Eloquent model’s database column is nullable, its automatic filter also gets `is-set` and `is-not-set`. Collections and custom sources have no schema and get neither.

A filter attached with a column’s `filter()` keeps its key, or takes the column’s key when it has none. It queries that column, including one with a `relationship()`, `count()` or `sum()` binding, unless it sets its own column with `column()` or a `$column` property. A column with an attached filter gets no automatic filter. Explicit table filters take precedence over attached and automatic filters with the same key.

## Select options

| Method                                                                 | Contract                                                                                                                                          |
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `options(array\|Closure $options, DataSourceKind\|string ...$sources)` | Explicit values or deferred options                                                                                                               |
| `multiple(bool $multiple = true)`                                      | Off by default. The filter’s own `equals` and `not-equals` take a list; a clause you add, replace or set takes one only with its own `multiple()` |
| `autoOptions(bool $auto = true, int $limit = 200)`                     | Discover bounded distinct values of the filter’s column; default off; limit must be positive                                                      |
| `supportsOptionSources(DataSourceKind\|string ...$sources)`            | Declare the kinds accepted by deferred options                                                                                                    |
| `addOption(string\|array\|Closure $value, ?string $label = null)`      | Append an option                                                                                                                                  |

The options go into the input of each `select` clause of the filter that has no options of its own, including a custom clause with `valueType('select')`. For exact primitive types, use explicit option records:

```php
SelectFilter::make('level')->options([
    ['value' => 1, 'label' => 'Level one'],
    ['value' => 2, 'label' => 'Level two'],
]);
```

Values can be strings, numbers, or Booleans. `"1"`, `1`, and `true` are distinct. A list uses a non-empty array of those values. Options describe the UI choices; backend input checks validate shape and type, not membership in the option list. Enforce application value restrictions in a clause validator or custom source. When distinct values exceed the `autoOptions()` limit, the input’s `optionsTruncated` is set and the UI displays a notice. Source-dependent option closures must declare the kinds they support.

### Custom sources

Use `options($callback, DataSourceKind::Custom)` for an application-owned source. `autoOptions()` requires a local dataset and is rejected. See [Source-specific options](https://docs.inertiax.dev/table/reference/source/#source-specific-options) for callback arguments and timing.

Custom sources implement comparisons from the validated filter conditions. See [Filters and options](https://docs.inertiax.dev/table/reference/source/#filters-and-options) for modifiers, validators, and storage maps.

## Boolean storage

Call `booleanMap(array $map)` on a `BooleanFilter` builder to override the stored values matching true and false. The built-in map recognizes true/false, 1/0, and strings such as `yes`/`no`, `on`/`off`, `enabled`/`disabled`, `active`/`inactive`, and `checked`/`unchecked`, plus `true`/`false` and `1`/`0`. The submitted filter value is still a Boolean.

For a text column storing custom labels:

```php
BooleanFilter::make('status')->booleanMap([
    true => ['published'],
    false => ['draft', 'archived'],
]);
```

Matching depends on the data source and column storage:

| Source / storage                                                  | Map entries used for comparison                                    |
| ----------------------------------------------------------------- | ------------------------------------------------------------------ |
| PostgreSQL `boolean`                                              | Booleans, `0`, `1`, `'0'`, and `'1'`, converted to Booleans        |
| `bit`: SQL Server’s Boolean, and `bit(1)` on MySQL and PostgreSQL | Booleans, `0`, `1`, `'0'`, and `'1'`, converted to `0` or `1`      |
| Numeric, including MySQL’s `tinyint(1)` Boolean                   | Numbers and numeric strings; Booleans become `0` or `1`            |
| Text, including SQL Server’s `nvarchar`                           | Scalar values converted to strings; Booleans become `'0'` or `'1'` |
| SQLite                                                            | Mixed stored types, compared using SQLite’s rules                  |
| Collection                                                        | Strict membership: `1`, `'1'`, and `true` are distinct map entries |

Except on SQLite and collections, incompatible entries are ignored. For the selected Boolean value, a nonempty list with no compatible entries raises an error. Null entries and empty lists are supported.

An unqualified column can be on the base table or a joined table. If multiple tables contain it, qualify it with its table or alias, such as `column('flags.enabled')`. For computed columns or subqueries whose storage cannot be determined, supply a [custom comparison](https://docs.inertiax.dev/table/guide/custom-filters/#add-a-custom-comparison).

## Filter groups

Basic mode accepts one root `and` group with direct conditions, including repeated field keys. Advanced mode accepts recursive `and`/`or` groups up to eight levels including the root. Both modes allow at most 100 total condition or child-group nodes. The server rejects unsupported grouping even if submitted outside the built-in UI.

## Shared clause routing

`applyClauseUsing(Closure $callback, DataSourceKind|string $source, ...$additionalSources)` wraps application of the selected clause. Its callback can receive `$source`, the filter’s `$column`, the filter’s `$key`, `$value`, `$clause`, `$clauseKey`, and Closure `$applyClause`. Delegate with `$applyClause($source, $column, $value)`, or pass another source or column. The selected clause still owns validation and its source requirements. See [Custom filters](https://docs.inertiax.dev/table/guide/custom-filters/#wrap-clause-application).

See [Callback context](https://docs.inertiax.dev/table/reference/callbacks/) for injectable arguments and resolution rules.
