Actions
Actions are declared on the server and may run for one row, an explicit bulk selection, or every selectable result matching the current search and filters.
Callback reference
| Method | Arguments | Invocation/result |
|---|---|---|
authorize | Request | Request-level permission; returns bool |
authorized | Row Model | Row permission; returns bool; callback is not invoked without a model |
disabled, hidden, disabledAndHidden | Row Model | Return bool; callback conditions resolve false without a model |
endpoint URL callback | Row Model | Return URL string or null; not called without a model |
| Dynamic label, tooltip, button class | Model or null | May be evaluated without a row, for example for bulk controls |
handle | Row Model, Selection | Once per processed row |
handleSelection | Selection | Once for the whole selection |
before | Selection | Once before handling; return value ignored |
after | Selection, handler result | Once after successful handling |
after receives the last processed row's return value for handle, not an array of all results. With no processed rows it receives null. For handleSelection, it receives that handler's return value. Returning null keeps the handler result; a non-empty string redirects. An exception before or during handling prevents after from running: it is not a finally hook.
Use authorize(Request) for request-wide access rather than a model callback that assumes a row exists. Model-specific types are not inferred from the table; these callback contracts use Eloquent Model.
Row actions
use Musing\InertiaTable\Actions\Action;
Action::make('delete', 'Delete')
->row()
->destructive()
->icon('Trash2')
->hideLabel()
->tooltip('Delete topic')
->authorized(
fn (Topic $topic) => auth()->user()->can('delete', $topic),
)
->handle(fn (Topic $topic) => $topic->delete())
->confirm(
'Delete topic?',
'This cannot be undone.',
'Delete',
'Cancel',
);authorized() is evaluated per model. Visibility, disabled state, and labels may also vary per row.
Bulk actions
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Musing\InertiaTable\Selection;
Action::make('archive', 'Archive')
->bulk()
->authorize(
fn (Request $request) =>
$request->user()->can('update', Topic::class),
)
->before(fn (Selection $selection) => Log::info('Archiving topics', [
'count' => $selection->count(),
]))
->handleSelection(fn (Selection $selection) => $selection
->query()
->update(['archived_at' => now()]))
->after(fn () => session()->flash('success', 'Topics archived'));handle() invokes the callback for each eligible model in chunks, preserving model events. handleSelection() invokes one callback with a typed Selection for set-based work. A set-based callback owns any extra per-model constraints that cannot be represented by selectableQuery().
before() and after() run once around the request. An after() callback may return a response or URL.
Request and row authorization
authorize()checks the request and action as a whole.authorized()checks a specific model.- hidden, disabled, unauthorized, and unselectable rows are skipped by managed per-model bulk handlers.
- authorization and availability are checked again at the signed action endpoint.
Managed endpoints pass through Laravel Response and Responsable values. Successful handlers without a response redirect back. Unexpected exceptions remain visible to Laravel's exception handler.
Selectability
Declare bulk eligibility at query and row level:
public function selectableQuery(Builder $query): Builder
{
return $query->whereNull('locked_at');
}
public function isSelectable(Model $model): bool
{
return $model->locked_at === null;
}Keep both rules equivalent. The query produces an exact selectableTotal; the row check disables individual checkboxes. An unselectable row may still expose row actions.
All-matching selection
The header checkbox selects every eligible result across pages immediately. Rows unchecked afterward are stored in selection.except. The browser sends a descriptor instead of loading every ID:
{
ids: [],
selection: {
all: true,
keys: [],
except: [42],
table: "topics",
state: {
search: "laravel",
filters: {
status: {
enabled: true,
clause: "equals",
value: "published",
},
},
},
},
}Managed handlers resolve this through Selection. Application-owned endpoints must resolve the descriptor safely and must not apply raw client attributes to SQL.
Confirmation copy
Confirmation fields support :count and scalar row attributes such as :name:
->confirm(
[
'Delete :count topic?',
'Delete :count topics?',
'Delete all :count matching topics?',
],
[
'This topic will be deleted.',
':count topics will be deleted.',
'All :count matching topics will be deleted.',
],
'Delete :count',
);The all-matching variant falls back to the plural variant when omitted.
Application routes
Use endpoint() when an existing route owns the workflow:
Action::make('edit')
->row()
->endpoint('get', fn (Topic $topic) => route('topics.edit', $topic));Omit both handle() and endpoint() for a frontend-owned action. <DataTable> emits custom-action with (action, keys, onFinish, selection). Call onFinish() after custom work completes.
Queued actions
Large managed bulk actions can be dispatched to Laravel's queue. Continue with queues.