Filters
Filters add controls to the listing's filter bar, above the free-text search. They are declared inside filters():
public function filters(): array
{
return [
TernaryFilter::make('is_published')
->label('Status')
->trueLabel('Published')
->falseLabel('Draft'),
SelectFilter::make('category')
->options(['news' => 'News', 'update' => 'Update', 'announcement' => 'Announcement']),
DateRangeFilter::make('published_at')
->label('Published between'),
];
}The Filter base class
Every filter implements type() (how the control is rendered on the frontend) and apply(Builder $query, mixed $value): Builder (how the chosen value is translated into a query clause). The label(string $label) method is common to all of them.
To override the matching logic of a specific filter without creating a new subclass:
SelectFilter::make('category')
->options([...])
->query(fn (Builder $query, mixed $value): Builder => $query->where('category', $value)->whereNotNull('published_at'))query() replaces apply() entirely when provided.
Built-in filters
SelectFilter
Filters by exact equality over a dropdown of options.
SelectFilter::make('category')
->options(['news' => 'News', 'update' => 'Update', 'announcement' => 'Announcement'])Internally: $query->where($this->name, $value).
TernaryFilter
Three states: yes / no / any. Meant for boolean columns.
TernaryFilter::make('is_published')
->trueLabel('Published') // default: "Yes"
->falseLabel('Draft') // default: "No"
->placeholder('Any status') // default: "Any"It accepts '1'/'true'/true as "yes" and '0'/'false'/false as "no"; any other value filters nothing.
DateRangeFilter
A from/to range over a date column.
DateRangeFilter::make('published_at')
->label('Published between')
->displayFormat('d/m/Y')The expected value is an array ['from' => ..., 'to' => ...]; either key is optional, and each one is translated into an independent whereDate('>=', …) or whereDate('<=', …).
RelationFilter
Filters the listing by a related record, chosen from a searchable combobox, the same component as the Relation field, but as a filter.
RelationFilter::make('author_id')
->related(User::class)
->titleAttribute('name')
->optionsQuery(fn (Builder $query): Builder => $query->whereNotNull('email_verified_at'))| Method | Description |
|---|---|
related(class-string<Model> $model) | The related model. |
titleAttribute(string $attribute) | Attribute shown in each option. name by default. |
searchColumns(string ...$columns) | Columns the combobox searches against. |
optionsQuery(Closure $callback) | Restricts which records appear in the picker. |
placeholder(string $text) | Text when nothing is selected. Any by default. |
optionsQuery() and not query() on purpose: the base Filter already has an inherited query(Closure) that overrides how the filter matches the records (see "The Filter base class" above); reusing that name for "which options are offered" would silently collide with it. RelationFilter serves its own options endpoint, separate from that of a Relation field, precisely because a filter and a field can share the same name (filtering by the same column that a form field already picks).
This first version only supports a single value, there is no ->multiple() variant yet.