Validation
Each field builds its own Laravel validation rules from its configuration. You add extra rules when you need them, and validate the request with one call.
TextInput::make('email')->email()->required()->rule('unique:users,email');
// Rules: ['required', 'string', 'email', 'unique:users,email']Examples
Each example is a live form built from the PHP below it. The docs have no server, so the preview only checks required fields in the browser. Every other rule shown here runs in Laravel when you call validate().
Basic
Presence, generated rules and your own rules on a few fields. website is not required, so it gets nullable and an empty value passes.
use Erag\InertiaForms\Fields\Checkbox;
use Erag\InertiaForms\Fields\TextInput;
use Illuminate\Validation\Rule;
[
TextInput::make('username')
->required()
->minLength(3)
->maxLength(20)
->rules('alpha_dash')
->rule(Rule::unique('users', 'username')),
TextInput::make('email')->email()->required(),
TextInput::make('website')->url()->help('Optional. Gets nullable, so an empty value passes.'),
Checkbox::make('terms')->label('I agree to the terms')->required(),
];Custom messages and attribute names
messages() replaces the text for one rule on one field, and attributes() renames the field inside Laravel's default messages. On the server, an empty email reads "We need your email to reply." and an invalid one reads "The email address field must be a valid email address."
use Erag\InertiaForms\Fields\Checkbox;
use Erag\InertiaForms\Fields\Submit;
use Erag\InertiaForms\Fields\Textarea;
use Erag\InertiaForms\Fields\TextInput;
use Erag\InertiaForms\Form;
class ContactForm extends Form
{
public function fields(): array
{
return [
TextInput::make('email')->label('Work email')->email()->required(),
Textarea::make('message')->minLength(20)->maxLength(1000)->showCharacterCount()->required(),
Checkbox::make('terms')->label('I agree to the privacy policy')->required(),
Submit::make('Send message'),
];
}
public function messages(): array
{
return [
'email.required' => 'We need your email to reply.',
'message.min' => 'Tell us a bit more (at least :min characters).',
'terms.accepted' => 'Please accept the privacy policy to continue.',
];
}
public function attributes(): array
{
return [
'email' => 'email address',
];
}
}Advanced: rules that follow visibility
Card and bank fields are both required, but only the visible group is validated. Pick "Bank transfer" and submit: the card fields are no longer checked. The coupon uses a closure rule, which only runs when a value is given.
use Erag\InertiaForms\Fields\DatePicker;
use Erag\InertiaForms\Fields\Fieldset;
use Erag\InertiaForms\Fields\Radio;
use Erag\InertiaForms\Fields\Submit;
use Erag\InertiaForms\Fields\TextInput;
use Erag\InertiaForms\Form;
class PaymentForm extends Form
{
public function fields(): array
{
return [
Radio::make('method')->label('Pay with')->buttons()->default('card')->required()->options([
'card' => 'Card',
'bank' => 'Bank transfer',
]),
Fieldset::make('Card')->visibleWhen('method', 'card')->columns(2)->fields([
TextInput::make('card.holder')->label('Name on card')->required()->columnSpan(2),
TextInput::make('card.number')->label('Card number')->required()->rules('digits_between:12,19'),
DatePicker::make('card.expires')->label('Expiry date')->required(),
]),
Fieldset::make('Bank transfer')->visibleWhen('method', 'bank')->fields([
TextInput::make('bank.iban')
->label('IBAN')
->required()
->rule('regex:/^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/'),
]),
TextInput::make('coupon')->rule(function (string $attribute, mixed $value, \Closure $fail): void {
if ($value !== null && strtoupper($value) !== 'WELCOME10') {
$fail('This coupon is not valid.');
}
}),
Submit::make('Pay now'),
];
}
}How rules are built
For every field, the rule list is built in this order:
- Presence:
requiredif you called->required(), otherwisenullable. - Type rules generated by the field (see the table below).
- Your rules from
->rules()and->rule(), in the order you added them.
Generated rules per field
| Field | Generated rules |
|---|---|
| TextInput | string, plus email or url for those types, min:/max: from minLength()/maxLength() |
TextInput with number() | numeric, min:/max: from min()/max() |
| Textarea | string, min:/max: from minLength()/maxLength() |
| Hidden | none |
| Combobox | in: the option values |
Combobox with multiple() | array on the field, in: option values on field.* |
| Radio | in: the option values |
| Checkbox / Toggle | boolean, or in: the custom true/false values |
| CheckboxGroup | array on the field, in: option values on field.* |
| DatePicker | date, after_or_equal:/before_or_equal: from minDate()/maxDate() |
| TimePicker | date_format:H:i (or H:i:s), after_or_equal:/before_or_equal: from minTime()/maxTime() |
| ColorPicker | regex:/^#[0-9a-fA-F]{6}$/ |
| Slider | numeric, min:, max: |
| FileUpload | file or image, extensions:, max: (per file); array and max: count when multiple() |
| Submit | no rules, no value |
You can inspect the result at any time:
ContactForm::make()->rules();
// [
// 'name' => ['required', 'string', 'max:50'],
// 'email' => ['required', 'string', 'email'],
// 'plan' => ['nullable', Illuminate\Validation\Rules\In { ... }],
// ...
// ]Adding rules
rules() accepts a pipe-separated string or an array. rule() adds one rule, which can be a string, a rule object, or a closure.
use Illuminate\Validation\Rule;
TextInput::make('username')
->required()
->rules('alpha_dash|min:3')
->rule(Rule::unique('users', 'username'));
TextInput::make('coupon')->rule(function (string $attribute, mixed $value, \Closure $fail) {
if ($value !== null && ! Coupon::valid($value)) {
$fail('This coupon has expired.');
}
});Pipes inside a rule
A string passed to rules() is split on |. For a regex that contains a pipe, use rule() or pass an array.
Required and nullable
->required()addsrequiredand shows a*next to the label.- Without it, the field gets
nullable, so an empty value passes and other rules are skipped for it.
Checkboxes and toggles
On a Checkbox or Toggle, required() means "must be switched on". It generates accepted (or in:<trueValue> for custom values), which is what you want for "I agree to the terms":
Checkbox::make('terms')->label('I agree to the terms')->required();Hidden fields are skipped
Only fields that are visible for the submitted data get rules. When a visibility condition hides a field (or its fieldset), that field is not validated and is not included in the validated data.
To decide what is visible, the form merges the request input over its initial values from data(). Fields that were not submitted fall back to their initial (or bound) value.
Unauthorized fields are removed completely, so they are never validated either.
Validating a request
$form->validate()
Call validate() with a request (or no argument to use the current request). It returns the validated data.
public function store(Request $request)
{
$data = ContactForm::make()->validate($request);
Message::create($data);
return back();
}On failure it throws a ValidationException. Laravel redirects back and Inertia passes the errors to <Form>, which shows each message under its field.
If the form itself is not authorized, validate() throws an AuthorizationException (HTTP 403).
The #[Validate] attribute
Put #[Validate] on a controller parameter typed as your form class. Laravel resolves the form from the container and validates the current request before your method runs.
use Erag\InertiaForms\Attributes\Validate;
public function store(#[Validate] ContactForm $form)
{
Message::create($form->validated());
return back();
}Using the attribute on a parameter that is not a Form subclass throws an InvalidArgumentException.
Update forms
The attribute creates a fresh form with no bound model and no route. When visibility or rules depend on the model being edited, validate manually instead:
public function update(Request $request, Post $post)
{
$post->update(EditPostForm::make()->bind($post)->validate($request));
return back();
}validated()
After a successful validate(), read the data again with validated(). Pass a key (dot notation works) to read one value, with an optional default.
$form->validated(); // all validated data
$form->validated('email'); // one value
$form->validated('address.city', 'N/A'); // nested value with a defaultOnly fields that had rules are included. Extra input that the form doesn't know about is dropped.
Error messages and attribute names
Field labels are used as attribute names in error messages. A field labelled "Work email" produces "The Work email field is required."
Override messages and attribute names by adding messages() and attributes() to your form:
class ContactForm extends Form
{
public function messages(): array
{
return [
'email.required' => 'We need your email to reply.',
'terms.accepted' => 'Please accept the terms to continue.',
];
}
public function attributes(): array
{
return [
'email' => 'email address',
];
}
}Entries in attributes() take priority over labels.
Showing errors
<Form> reads Inertia's error bag and shows the first error under each field. Errors for array items (like tags.0) are shown under the parent field (tags). When the user changes a field, its errors (including item errors) are cleared.