To validate a form in Livewire on Laravel 13, add #[Validate] attributes with Laravel rules to your component properties, call $this->validate() before saving, and show errors with Blade's @error directive. For larger forms, move the fields and rules into a Livewire\Form object; for instant feedback, bind inputs with wire:model.live.blur.
Livewire validation uses the same rule strings and rule objects as normal Laravel validation, so everything you know about form requests carries over.
Prerequisites
- Laravel 13 with PHP 8.3 or newer.
- Livewire 4, installed with
composer require livewire/livewire. - A layout that loads Livewire's scripts (Livewire injects them automatically on pages that use components).
Step 1: Create a class-based component
Livewire 4 creates single-file components by default. This tutorial uses a class-based component because it keeps PHP and Blade apart, which is easier to follow:
php artisan make:livewire ContactForm --classThat creates app/Livewire/ContactForm.php and resources/views/livewire/contact-form.blade.php. The validation code is identical in single-file components.
Step 2: Add rules with the Validate attribute
<?php
namespace App\Livewire;
use App\Models\ContactMessage;
use Livewire\Attributes\Validate;
use Livewire\Component;
class ContactForm extends Component
{
#[Validate('required|string|min:2|max:100')]
public string $name = '';
#[Validate('required|email:rfc|max:255')]
public string $email = '';
#[Validate('required|string|min:10|max:2000', message: 'Please write at least a short sentence.')]
public string $body = '';
public bool $sent = false;
public function save(): void
{
$validated = $this->validate();
ContactMessage::create($validated);
$this->reset('name', 'email', 'body');
$this->sent = true;
}
public function render()
{
return view('livewire.contact-form');
}
}When a property bound with wire:model is updated, Livewire validates that property against its attribute. Calling $this->validate() in save() checks every rule, including fields the user never touched, and returns only the validated values. The sent property has no attribute, so it is never part of the validated data.
Step 3: Show errors in the view
<form wire:submit="save">
@if ($sent)
<p class="success">Thanks, your message was sent.</p>
@endif
<label for="name">Name</label>
<input id="name" type="text" wire:model.live.blur="name">
@error('name') <p class="error">{{ $message }}</p> @enderror
<label for="email">Email</label>
<input id="email" type="email" wire:model.live.blur="email">
@error('email') <p class="error">{{ $message }}</p> @enderror
<label for="body">Message</label>
<textarea id="body" wire:model="body" rows="5"></textarea>
@error('body') <p class="error">{{ $message }}</p> @enderror
<button type="submit" wire:loading.attr="disabled">Send</button>
</form>wire:model.live.blur sends the value when the field loses focus, so the user sees an error right after leaving a field instead of after submitting. The body field uses plain wire:model, so it is only checked on submit. I prefer blur over .live on every keystroke because it avoids a network request per character.
Render the component in any Blade page with <livewire:contact-form />.
When should you use rules() instead of attributes?
Attributes must contain constant values. If a rule depends on runtime data, such as a Rule::unique() that ignores the current record, define a rules() method:
use Illuminate\Validation\Rule;
protected function rules(): array
{
return [
'email' => ['required', 'email', Rule::unique('subscribers', 'email')->ignore($this->subscriberId)],
];
}Rules from rules() run when you call $this->validate(). If you also want real-time validation for that property, add an empty #[Validate] attribute to it.
Step 4: Move bigger forms into a form object
Once a component has many fields, put them in a form object so the component stays readable and the form can be reused for create and edit screens.
<?php
namespace App\Livewire\Forms;
use Livewire\Attributes\Validate;
use Livewire\Form;
class ContactFormData extends Form
{
#[Validate('required|string|min:2|max:100')]
public string $name = '';
#[Validate('required|email:rfc|max:255')]
public string $email = '';
#[Validate('required|string|min:10|max:2000')]
public string $body = '';
}public ContactFormData $form;
public function save(): void
{
$validated = $this->form->validate();
ContactMessage::create($validated);
$this->form->reset();
}In the view, prefix fields with form.: wire:model.live.blur="form.email" and @error('form.email').
Common mistakes
- Forgetting
$this->validate()in the action. Real-time validation only checks fields that changed. Without the final call, untouched required fields slip through. - Wrong error key with form objects. Use
@error('form.email'), not@error('email'). - Dynamic values in attributes. PHP attributes cannot call functions. Use
rules(). - Trusting public properties. Users can change any public property from the browser. Validate everything you save and lock ids with
#[Locked].
How do you verify it works?
<?php
use App\Livewire\ContactForm;
use Livewire\Livewire;
test('the contact form requires all fields', function () {
Livewire::test(ContactForm::class)
->call('save')
->assertHasErrors(['name' => 'required', 'email' => 'required', 'body' => 'required']);
});
test('a valid message is saved', function () {
Livewire::test(ContactForm::class)
->set('name', 'Sara')
->set('email', 'sara@example.com')
->set('body', 'Hello, I would like a quote.')
->call('save')
->assertHasNoErrors()
->assertSet('sent', true);
$this->assertDatabaseHas('contact_messages', ['email' => 'sara@example.com']);
});In the browser, leave the email field with an invalid value and confirm the error appears without submitting.
How do you validate arrays and nested data?
Livewire supports Laravel's wildcard rules. For a list of email inputs stored in public array $emails = [];, add #[Validate(['emails' => 'required|array|min:1', 'emails.*' => 'required|email'])] above the property. Bind each input with wire:model.live.blur="emails.{{ $index }}" and show errors with @error('emails.' . $index). The same dot syntax works for nested arrays inside form objects, such as form.address.city.
For uploads, Livewire validates temporary files with the usual file, image and max rules before you store them, so the checks you use in controllers carry over here as well. Store the validated file with $this->photo->store('photos', 'public') once validation passes.
FAQ
Can I use a Laravel form request in Livewire?
Not directly, because there is no HTTP request per action. Put the rules in attributes, rules() or a form object instead.
How do I clear errors?
Call $this->resetValidation() for all fields or pass a field name to clear one.
How do I rename a field in error messages?
Use the as: argument, for example #[Validate('required', as: 'full name')].



