Skip to content

Record actions

Record actions work on one record. A resource returns them from actions().

use Anvil\Resource\Action;
use Anvil\Schema\Fields\DateInput;
use Anvil\Schema\Form;
public function actions(): array
{
return [
Action::make('finish')
->label('Finish')->icon('flag')->color('success')
->visible(fn ($project): bool => $project['status'] !== 'done')
->confirm('Finish the project?')
->inRow()->primary()
->handle(function ($project): string {
$this->model()->update($this->recordKey($project), ['status' => 'done']);
return 'Project finished.';
}),
Action::make('leave')
->label('Record exit')->icon('box-arrow-right')->color('outline-danger')
->permission('people.lifecycle')
->form(static fn (Form $form): Form => $form->schema([
DateInput::make('exit_date')->required()->default(date('Y-m-d')),
]))
->handle(function ($person, array $data): string {
service('iamLifecycle')->leave((int) $this->recordKey($person), (string) $data['exit_date']);
return 'The exit has been recorded.';
}),
];
}

Action::make() takes a camelCase name ([a-z][a-zA-Z0-9]*, at most 41 characters; otherwise an InvalidArgumentException). It identifies the action at the endpoint, in the audit log and in the API (markDone → /actions/mark-done). Names must be unique within the resource.

Option Effect Default
label('…') button text the name with a capital letter
icon('flag') Bootstrap icon none
color('success') button colour, see below outline-secondary
permission('people.lifecycle') required permission <slug>.update
visible(fn ($record) => …) only shown and executable when it returns true always
confirm('…') question before the action runs none
form(fn (Form $form, $record) => …) dialog with fields, see Actions with a form none
inRow() also in the rows and cards of the list detail page only
primary() shown as button in rows and cards before others
link('hr', '{value}', fn ($record) => …, target: …) opens a page of an external system
url(fn ($record) => …, target: …) opens a page of the application
handle(fn ($record, array $data) => …) the code that runs

Colours: primary, secondary, success, danger, warning, info, light, dark, outline-primary, outline-secondary, outline-success, outline-danger, outline-warning. Others throw an InvalidArgumentException. Use danger colours for actions that remove or end something.

An action has either a handler or a link. An action with neither throws a LogicException when it runs.

->handle(function ($record, array $data): ?string {
// ...
return 'Done.'; // message shown to the user; null shows "Done"
})
  • $record is the record as the model returns it (array or entity), loaded through the resource.
  • $data holds the validated, converted values of the form (an empty array without a form), e.g. the internal id for a BelongsTo.
  • The handler runs in a database transaction through the component endpoint and is written to the audit log as component.action with the action name. Changes through Anvil models are audited as well.
  • An exception rolls everything back. Throw EndpointException::forValidation(['field' => 'message']) for an error at a field of the dialog.
  • On the detail page the user is redirected to the detail page and sees the message; in the list the list is reloaded.

Keep handlers short: call a service that holds the business logic, so the same logic can be used by commands, jobs and the API.

// Hand over a document for signing (see Documents)
Action::make('issue')->label('Issue')->icon('box-arrow-right')->permission('receipts.manage')
->handle(function ($person): string {
$document = service('documents')->create(new IssueReceipt(), $this, $person, [...]);
service('handover')->start($this->panel(), $document, $this->path('view', $this->recordUuid($person)));
return 'The receipt is ready for signing.';
}),
// Notify somebody (see Notifications)
Action::make('remind')->icon('bell')
->handle(function ($task): string {
service('notifications')->send((int) $task['assignee_id'], Notification::make('Please finish ' . $task['title']));
return 'Reminder sent.';
}),