Forms
The form of a resource is used for creating, editing and – read-only – for
the detail page. You describe it once in form(); Anvil derives the inputs,
the validation rules and the data that is saved from it. Dialogs of
actions use the same fields.
use Anvil\Schema\Fields\BelongsTo;use Anvil\Schema\Fields\DateInput;use Anvil\Schema\Fields\NumberInput;use Anvil\Schema\Fields\Select;use Anvil\Schema\Fields\Textarea;use Anvil\Schema\Fields\TextInput;use Anvil\Schema\Fields\Toggle;use Anvil\Schema\Form;use Anvil\Schema\Layout\Section;
public function form(Form $form): Form{ return $form->schema([ Section::make('Invoice')->description('Basic data')->columns(2)->schema([ TextInput::make('number')->required()->maxLength(30), BelongsTo::make('customer_id')->label('Customer')->relationship(CustomerModel::class, 'name')->required(), Select::make('status')->options(['draft' => 'Draft', 'sent' => 'Sent', 'paid' => 'Paid'])->required()->default('draft'), NumberInput::make('amount')->currency('EUR')->min(0), DateInput::make('due_on'), Toggle::make('reminders')->label('Send reminders'), Textarea::make('notes')->columnSpanFull(), ]), ]);}Fields
Section titled “Fields”Every field has a page of its own with all options, the saved value, the validation, the read-only display and the API representation.
| Field | Use it for | Saved value |
|---|---|---|
| TextInput | single line text, e-mail, URL, phone, plain numbers | string |
| Textarea | longer plain text | string |
| MarkdownEditor | formatted text with toolbar and preview | Markdown string |
| NumberInput | locale aware numbers and money | decimal string or integer |
| Select | one value of a fixed list | the value |
| Radio | one value of a few options, all visible | the value |
| CheckboxList | several values of a fixed list, optionally grouped | list of values |
| Toggle | yes / no | 0 / 1 |
| DateInput | date, or date and time | Y-m-d / Y-m-d H:i:s |
| BelongsTo | a record of another model (foreign key) | internal id |
| UserSelect | a user of the current tenant | internal user id |
| TagsInput | free text tags | JSON list |
| Repeater | a short list of small records, or records chosen by search | JSON list of objects |
| FileUpload | one file per record and field | – (a file of the record) |
| PasswordInput | a password that is never rendered back | the password as entered |
All fields are in Anvil\Schema\Fields and are created with
::make('column'). The name must be a plain column name
([a-z_][a-z0-9_]*, at most 64 characters, otherwise an
InvalidArgumentException); the label defaults to the humanized name
(due_on → “Due on”). Own fields: Extending Anvil.
Options of every field
Section titled “Options of every field”| Option | Effect |
|---|---|
label('…') |
Label and name in validation messages |
required() |
Adds required, otherwise permit_empty; shows a red asterisk |
rules('alpha_dash', 'is_unique[t.c]') |
Additional CodeIgniter rules; | separated strings work too |
default($value) |
Value of the field when creating (not when editing) |
helperText('…') |
Help text below the input (not on the detail page) |
placeholder('…') |
Placeholder inside the input; for selects the label of the empty option |
disabled() |
Shown, never written |
disabledOn('edit') |
Disabled in the given operations (create, edit) |
hiddenOn('create') |
Not shown, not validated and not written in the given operations (create, edit, view) |
virtual() |
Not a column: validated and passed to Resource::afterSave(), see Virtual fields |
columnSpan(2), columnSpanFull() |
Width in multi column layouts, see Layout |
Values are always escaped when they are rendered. Empty values are shown as “—” on the detail page.
Operations
Section titled “Operations”A form is rendered for create, edit or view (the detail page). Use
the constants of Anvil\Schema\FormContext:
use Anvil\Schema\FormContext;
TextInput::make('cost_center')->required()->disabledOn(FormContext::EDIT), // fixed after creationSelect::make('status')->options($statuses)->disabled()->hiddenOn(FormContext::CREATE), // set by the systemPasswordInput::make('password')->required()->hiddenOn(FormContext::EDIT, FormContext::VIEW),In view every field shows its value read-only; layouts work the same in
all operations. More in Visibility per operation.
What is saved
Section titled “What is saved”The saved data is built from the fields that are writable in the current
operation, not from the request: each field converts its validated input
(dehydrate), e.g. a NumberInput turns 1.234,50
into "1234.50", a BelongsTo turns a UUID into the
internal id. A client cannot write tenant_id, uuid or any other column
that is not part of the form, even if it is in $allowedFields of the
model. Disabled and hidden fields are left out the same way. Details:
Validation.
Virtual fields
Section titled “Virtual fields”A field marked virtual() is validated and converted like the others but
not written to the model, e.g. the permissions of a role kept in another
table. The resource receives its value in afterSave() and fills it when
editing with formValues():
CheckboxList::make('items')->groupedOptions($this->itemOptions())->columns(3)->virtual(),See Extension points. FileUpload is always virtual.
Forms in action dialogs
Section titled “Forms in action dialogs”Actions, bulk actions and relation header actions open a dialog when they have a form. It is built with the same fields and rules:
Action::make('reassign') ->form(fn (Form $form, $order) => $form->schema([ BelongsTo::make('user_id')->label('New owner')->relationship(UserAccountModel::class, 'username'), Textarea::make('reason')->required(), ])) ->handle(function ($order, array $data): string { service('orders')->reassign($order, $data['user_id'], $data['reason']);
return 'The order has been reassigned.'; }),The handler receives the validated, converted data, e.g. the internal id
for a BelongsTo, and returns a success message or null. See
Actions.
History
Section titled “History”The change history of the detail page formats values with the form fields:
titles for BelongsTo, labels for options, locale formatted numbers.
Columns that are not in the form and look like references (*_id) are
listed as “changed” without their value, so internal ids never reach the
browser.
With the API enabled for a
resource, its form also describes the JSON representation and the accepted
input: the same fields, rules and conversions apply. Each field page has a
section on its API value. PasswordInput is write-only, FileUpload is not
part of the API.