Skip to content

Actions

new ComponentAction(
name: 'rename', // [a-z][a-zA-Z0-9_-]{0,63}
permission: 'projects.update', // required, never empty
handler: $this->rename(...), // Closure(ComponentRequest): ComponentResponse
rules: ['name' => 'required|string|max_length[100]'],
mutates: true, // default
);
Parameter Description
name the value of _action; an invalid name throws an InvalidArgumentException
permission checked with the gate before the handler runs (403 otherwise). Gate::AUTHENTICATED ('@authenticated') allows every logged in user; use it only for the user’s own data
handler usually a private method as first-class callable; other public methods of the component are never reachable
rules CodeIgniter validation rules per input field (string, list, or ['label' => …, 'rules' => …]); failures answer 422 with field errors
mutates true: the handler runs in a database transaction and the call is audited as component.action. false for read-only actions (loading, searching, counting)

The handler only receives input that has a rule; everything else is dropped. An action without rules receives no input at all.

$request->string('name'); // validated scalar as string, '' if missing
$request->string('mode', 'compact'); // with default
$request->input; // all validated input, nested fields included

actions() runs on every request, so actions can be built from data. The task inbox (Anvil\Tasks\Ui\InboxComponent) creates one action per open task (complete-<uuid>) with the rules of that task’s form. Keep actions() cheap: it is called before the permission check.

Throw Anvil\Endpoint\EndpointException from a handler:

Factory Status Shown to the user
EndpointException::forValidation(['field' => 'Message']) 422 messages next to the fields, see Forms in a component
EndpointException::forForbidden($reason) 403 generic message
EndpointException::forNotFound($reason) 404 generic message

$reason is internal: it is written to the security log, never sent to the browser. In a mutating action, every exception rolls back the transaction, including changes made before it was thrown.

Action Permission
reads or changes data of a resource the permission of the data: tickets.view, tickets.update
something special, e.g. approving an own permission, e.g. tickets.approve; add it to Config\Anvil::$permissions so it appears in the role editor
only the user’s own data (profile, own preferences) Gate::AUTHENTICATED

The permission of the action is checked in addition to the permission of the page. A policy for the record is not checked automatically – call the gate in the handler when the decision depends on the record:

private function close(ComponentRequest $request): ComponentResponse
{
$ticket = (new TicketModel())->findByUuid($request->claims->record)
?? throw EndpointException::forNotFound('ticket');
if (! service('gate')->allows('tickets.update', $ticket)) {
throw EndpointException::forForbidden('policy');
}
// …
}
  • Bound to one record: take it from $request->claims->record, never from the input.
  • Chosen by the user (a list): send its UUID with the rule valid_uuid and look it up through the Anvil model, which applies the tenant scope.
  • Never accept internal ids in the input.