Skip to content

Actions with a form

form() opens a modal dialog before the action runs. The fields work like in forms: validation rules are derived from the fields (required(), maxLength(), …) and invalid input is shown in the dialog (422). The handler gets the converted values.

Action::make('addRole')
->label('Add role')->icon('plus-circle')
->permission('people.lifecycle')
->form(static fn (Form $form): Form => $form->schema([
BelongsTo::make('business_role_id')->label('Role')->relationship(BusinessRoleModel::class, 'name')->required(),
DateInput::make('valid_from')->required()->default(date('Y-m-d')),
DateInput::make('valid_until'),
Textarea::make('reason')->maxLength(500),
]))
->handle(function ($person, array $data): string {
// $data['business_role_id'] is the id of a role the user may see.
service('roles')->grant($person, $data);
return 'Role added.';
}),

All fields work, including searchable BelongsTo, UserSelect and lookup repeaters (their searches are actions of the same component with the permission of the action). Layouts such as sections or grids work too; keep dialogs short.

  • Defaults (default()) fill the dialog; the operation is create.
  • FileUpload is not supported in dialogs.
  • The values are converted like in forms: numbers normalised, BelongsTo to ids, repeaters to JSON.
  • Virtual fields are passed in $data as well.

On the detail page the form closure receives the record, so the dialog can depend on it – e.g. options with the current grants of a person, or the items the person already has:

->form(fn (Form $form, array|object $person): Form => $form->schema([
CheckboxList::make('ok')->options($this->assignedItems($person)),
]))

In rows and cards, one dialog per action serves all rows of the list: its form is built once without a record (the closure receives an empty array) and the clicked row sets the record when the dialog opens. Forms of row actions must therefore not depend on the record. If building the form without a record fails, the action is not offered in rows; it stays available on the detail page.

With confirm() the question is asked when the dialog is submitted. Without a form, confirm() asks before the request is sent.