Table
Editing & Column-Level Filters
Beyond the dedicated Filter classes, any column can have an inline filter in its header.
Column-Level Filtering
Beyond the dedicated Filter classes, any column can have an inline filter in its header.
// Select filter in column header (pick one)TextColumn::make('status') ->filterable() ->filterAsSelect(['active' => 'Active', 'inactive' => 'Inactive']) // Multi-select filter (pick several → matches any, whereIn). Renders the same// searchable combobox as the wire-forms Select — search is on by default.BadgeColumn::make('role') ->filterAsMultiSelect([ 'admin' => 'Administrator', 'editor' => 'Editor', 'viewer' => 'Viewer', ], 'Any role') ->filterSearchable(false) // opt out of the search box for a short list // Boolean filterBooleanColumn::make('is_active') ->filterable() ->filterAsBoolean() // Date range filterTextColumn::make('created_at') ->filterable() ->filterAsDateRange() // Number range filterTextColumn::make('price') ->filterable() ->filterAsNumberRange(0, 10000) // min, max, optional step // Custom filter logicTextColumn::make('name') ->filterable() ->filterUsing(fn (Builder $query, mixed $value) => $query->where('name', 'like', "%{$value}%")) ->filterDebounce(500) // Filter with operatorTextColumn::make('age') ->filterable() ->filterOperator('>=')
Column-Level Filter API
A column header filter is a placement of a canonical Filter — the filterAs*()
helpers are thin factories over TextFilter / SelectFilter / DateFilter /
NumberRangeFilter / TernaryFilter, or pass a ready one with ->filter(). See
Column-Level Filters for the shared engine, chips
and query-string persistence.
->filterable(bool $filterable = true, string $type = 'text', array|string $options = [])->isFilterable(): bool->filter(Filter $filter) // attach a ready-made canonical filter->getFilter(): ?Filter->filterAsSelect(array|string $options, ?string $placeholder = null) // single value; searchable combobox->filterAsMultiSelect(array|string $options, ?string $placeholder = null) // several values (whereIn); searchable combobox->filterSearchable(bool $condition = true) // toggle the in-panel search (on by default)->filterAsDate(string|DateTimeInterface|null $minDate = null, string|DateTimeInterface|null $maxDate = null)->filterAsDateRange(string|DateTimeInterface|null $minDate = null, string|DateTimeInterface|null $maxDate = null)->filterAsNumberRange(?float $min = null, ?float $max = null, ?float $step = null)->filterAsBoolean(?string $trueLabel = null, ?string $falseLabel = null)->filterOperator(string $operator) // '=', '!=', '>', '<', '>=', '<=', 'like' (default, partial match), 'starts_with', 'ends_with'->filterDebounce(int $ms)->filterPlaceholder(?string $placeholder)->filterUsing(Closure $fn) // fn(Builder $query, mixed $value)
Inline Editing
The editor comes from the column type, not from a setting: use TextInputColumn, SelectColumn, ToggleColumn or CheckboxColumn. An ordinary column renders no editor.
editable() is the switch that turns a dedicated column's editor on and off,
and the server-side gate for writing that column:
TextInputColumn::make('name') ->editable(fn () => auth()->user()->isAdmin()) // false renders the plain value ->editableRules(fn ($record) => ['required', 'max:255']) ->editableUsing(function ($record, $column, $value) { $record->update([$column => $value]); })
Naming an editor type — editable(true, 'select', […]) — throws: no view has
ever read one, so it would silently do nothing. Use SelectColumn instead.
The options argument of filterable() / filterAsSelect() accepts a PHP enum
class — it expands to value => label exactly like the dedicated
SelectColumn/SelectFilter. See Enum Options.
How inline saves work
Saving a cell (updateTableCell) re-renders the table, and the cell protects its own state
while that happens. Everything derived from the written value — a summary, a rollup, a badge
computed from the same column, the row's position under the current sort — is stale the moment the
edit lands, and only a render can put it right.
The cell survives the morph because its root carries wire:ignore.self, so Livewire leaves its
attributes and its Alpine state alone. That is also why the value the server just rendered cannot
reach the cell through that root: it is delivered on a sync node, a small child element the
morph does update, which the cell watches. One shared Alpine component (wireEditableCell) does
all of this — text inputs, selects and toggles use it, so they behave consistently.
- Optimistic + rollback. The cell shows the new value immediately, then calls the server; if the save fails (validation, permission, error) it rolls back to the last server-confirmed value and surfaces the message.
- Optimistic locking. Each edit carries the row's version (
updated_at, resolved through the model's own timestamp column, soconst UPDATED_ATis honoured). If the row changed since the page loaded, the save is rejected as a conflict: the cell loads the current value and shows the conflict message inline on the cell itself (a red state on the text/select/toggle, no toast orNotificationManagersetup required) — so two people (or two quick edits that bump the row) can't silently clobber each other. Any re-render — a poll tick, a modal write, another session's change arriving — refreshes each cell's value and its version through the sync node, so the next edit is compared against what is actually in the database. Opt in to also raise a (more prominent) toast for conflicts withTable::notifyEditConflicts()— this one needs the notification system wired up (a toast container); the inline message works without it. - Opting out of the render.
Table::refreshAfterEdit(false)goes back to answering an edit with no HTML at all. Worth it only for a table where the query behind a render is expensive and nothing on screen depends on the edited value: the cell still reconciles itself from the response, nothing around it does. - Server-side authorization. The client
disabled()state is only cosmetic — a per-recorddisabled()cell (and any column permission) is enforced again on the server inupdateTableCell, so a forged request can't write to a locked cell.