Introduction
Inertia bridges Laravel's server-side validation with your Vue or React form without forcing you to build a REST API. The useForm helper gives you a reactive form object that knows how to POST to your controllers, surface validation errors, and track loading state — all while letting Laravel's validate() method do the work it's good at.
Key Concepts
useForm: A reactive form helper imported from@inertiajs/vue3(or@inertiajs/react) that wraps field state, submission, validation errors, and processing flags.- Server-side validation: Laravel's
$request->validate([...])method. When validation fails, Inertia's request adapter converts the redirect-with-errors response into populatedform.errorsfields client-side. form.processing: A reactive boolean that istruewhile the request is in flight. Disable the submit button with it to prevent double submissions.
Real World Context
A broken signup form costs conversions. With Inertia, you get instant inline error messages for every field (validated server-side so the rules stay authoritative) and a guaranteed-correct disabled state while the request is pending — without shipping a JSON API.
Deep Dive
The Controller
The controller looks exactly like a classic Laravel form handler:
phppublic function store(Request $request) { $validated = $request->validate([ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'email', 'unique:users,email'], 'password' => ['required', 'min:8', 'confirmed'], ]); User::create($validated); return redirect()->route('users.index') ->with('success', 'User created'); }
No API resource, no JSON response — Inertia intercepts the validation redirect and surfaces the errors on the frontend automatically.
The Vue Form
vue<script setup> import { useForm } from '@inertiajs/vue3' const form = useForm({ name: '', email: '', password: '', password_confirmation: '', }) const submit = () => { form.post('/users', { onSuccess: () => form.reset('password', 'password_confirmation'), }) } </script> <template> <form @submit.prevent="submit"> <label> Name <input v-model="form.name" type="text"> <span v-if="form.errors.name" class="text-red-600">{{ form.errors.name }}</span> </label> <label> Email <input v-model="form.email" type="email"> <span v-if="form.errors.email" class="text-red-600">{{ form.errors.email }}</span> </label> <label> Password <input v-model="form.password" type="password"> <span v-if="form.errors.password" class="text-red-600">{{ form.errors.password }}</span> </label> <label> Confirm Password <input v-model="form.password_confirmation" type="password"> </label> <button type="submit" :disabled="form.processing"> {{ form.processing ? 'Creating…' : 'Create user' }} </button> </form> </template>
Three things to notice:
form.errors.nameis populated automatically from Laravel's validation response.form.processingis a reactive flag flipped during the request.form.reset('password', 'password_confirmation')clears sensitive fields after a successful submit.
Partial Reloads and Preservation
Inertia's request options let you preserve scroll position, keep specific form fields, and reload only specific props:
javascriptform.post('/users', { preserveScroll: true, preserveState: (page) => Object.keys(page.props.errors).length > 0, })
Here we preserve scroll on any response and preserve component state (so the form doesn't reset) only when the response includes validation errors.
Common Pitfalls
- Hand-rolling
axios.postand manually parsing errors — You loseform.processing, reset helpers, and the built-in error mapping. UseuseForminstead. - Resetting the whole form on success — Calling
form.reset()with no arguments clears every field. Pass specific field names for password flows. - Double submissions — Forgetting
:disabled="form.processing"lets an impatient user click twice and create duplicate records.
Best Practices
- Trust the server — Let Laravel's
validate()method be the only source of truth for validation rules. Client-side echoes are fine, but the server must always validate. - Use
onSuccess/onErrorcallbacks — They give you a single place to reset fields, show toasts, or redirect after the response. - Name your form fields to match Laravel's — Inertia maps
form.errors.Xto validation errors keyed byX. Keep them aligned.
Summary
useFormwraps field state, submission, errors, and loading in a single reactive object.- Laravel's
$request->validate()remains the source of truth — Inertia surfaces errors client-side automatically. form.processingdisables the submit button during the request, preventing double submissions.- Use
preserveScroll/preserveStateoptions to fine-tune the user experience.