You're viewing the Inertia.js v2.0 documentation. Upgrade guide →

Forms

Inertia provides two primary ways to build forms: the <Form> component and the useForm helper. Both integrate with your server-side framework's validation and handle form submissions without full page reloads.

Form component

Inertia provides a <Form> component that behaves much like a classic HTML form, but uses Inertia under the hood to avoid full page reloads. This is the simplest way to get started with forms in Inertia.

<script setup>
import { Form } from '@inertiajs/vue3'
</script>

<template>
  <Form action="/users" method="post">
    <input type="text" name="name" />
    <input type="email" name="email" />
    <button type="submit">Create User</button>
  </Form>
</template>

Just like a traditional HTML form, there is no need to attach a v-model to your input fields, just give each input a name attribute and the Form component will handle the data submission for you.

The component also supports nested data structures, file uploads, and dotted key notation.

<Form action="/reports" method="post">
  <input type="text" name="name" />
  <textarea name="report[description]"></textarea>
  <input type="text" name="report[tags][]" />
  <input type="file" name="documents" multiple />
  <button type="submit">Create Report</button>
</Form>

You can pass a transform prop to modify the form data before submission. This is useful for injecting additional fields or transforming existing data, although hidden inputs work too.

<Form
  action="/posts"
  method="post"
  :transform="data => ({ ...data, user_id: 123 })"
>
  <input type="text" name="title" />
  <button type="submit">Create Post</button>
</Form>

Wayfinder

When using Wayfinder, you can pass the resulting object directly to the action prop. The Form component will infer the HTTP method and URL from the Wayfinder object.

<script setup>
import { Form } from '@inertiajs/vue3'
import { store } from 'App/Http/Controllers/UserController'
</script>

<template>
  <Form :action="store()">
    <input type="text" name="name" />
    <input type="email" name="email" />
    <button type="submit">Create User</button>
  </Form>
</template>

Checkbox inputs

When working with checkboxes, you may want to add an explicit value attribute such as value="1". Without a value attribute, checked checkboxes will submit as "on", which some server-side validation rules may not recognize as a proper boolean value.

Slot props

The <Form> component exposes reactive state and helper methods through its default slot, giving you access to form processing state, errors, and utility functions.

<Form
  action="/users"
  method="post"
  #default="{
    errors,
    hasErrors,
    processing,
    progress,
    wasSuccessful,
    recentlySuccessful,
    setError,
    clearErrors,
    resetAndClearErrors,
    defaults,
    isDirty,
    reset,
    submit,
  }"
>
  <input type="text" name="name" />

  <div v-if="errors.name">
    {{ errors.name }}
  </div>

  <button type="submit" :disabled="processing">
    {{ processing ? 'Creating...' : 'Create User' }}
  </button>

  <div v-if="wasSuccessful">User created successfully!</div>
</Form>

The defaults method allows you to update the form's default values to match the current field values. When called, subsequent reset() calls will restore fields to these new defaults, and the isDirty property will track changes from these updated defaults. Unlike useForm, this method accepts no arguments and always uses all current form values.

The errors object uses dotted notation for nested fields, allowing you to display validation messages for complex form structures.

<Form action="/users" method="post" #default="{ errors }">
  <input type="text" name="user.name" />
  <div v-if="errors['user.name']">{{ errors['user.name'] }}</div>
</Form>

Props and options

In addition to action and method, the <Form> component accepts several props. Many of them are identical to the options available in Inertia's visit options.

<Form
  action="/profile"
  method="put"
  error-bag="profile"
  query-string-array-format="indices"
  :headers="{ 'X-Custom-Header': 'value' }"
  :show-progress="false"
  :transform="data => ({ ...data, timestamp: Date.now() })"
  :invalidate-cache-tags="['users', 'dashboard']"
  disable-while-processing
  :options="{
    preserveScroll: true,
    preserveState: true,
    preserveUrl: true,
    replace: true,
    only: ['users', 'flash'],
    except: ['secret'],
    reset: ['page'],
  }"
>
  <input type="text" name="name" />
  <button type="submit">Update</button>
</Form>

Some props are intentionally grouped under options instead of being top-level to avoid confusion. For example, only, except, and reset relate to partial reloads, not partial submissions. The general rule: top-level props are for the form submission itself, while options control how Inertia handles the subsequent visit.

When setting the disable-while-processing prop, the Form component will add the inert attribute to the HTML form tag while the form is processing to prevent user interaction.

To style the form while it's processing, you can target the inert form in the following ways.

<Form
    action="/profile"
    method="put"
    disableWhileProcessing
    className="inert:opacity-50 inert:pointer-events-none"
>
    {/* Your form fields here */}
</Form>

Events

The <Form> component emits all the standard visit events for form submissions.

<Form
  action="/users"
  method="post"
  @before="handleBefore"
  @start="handleStart"
  @progress="handleProgress"
  @success="handleSuccess"
  @error="handleError"
  @finish="handleFinish"
  @cancel="handleCancel"
  @cancelToken="handleCancelToken"
>
  <input type="text" name="name" />
  <button type="submit">Create User</button>
</Form>

Resetting the Form

The Form component provides several attributes that allow you to reset the form after a submission.

`resetOnSuccess` may be used to reset the form after a successful submission.

<!-- Reset the entire form on success -->
<Form action="/users" method="post" resetOnSuccess>
  <input type="text" name="name" />
  <input type="email" name="email" />
  <button type="submit">Submit</button>
</Form>

<!-- Reset specific fields on success -->
<Form action="/users" method="post" :resetOnSuccess="['name']">
  <input type="text" name="name" />
  <input type="email" name="email" />
  <button type="submit">Submit</button>
</Form>

`resetOnError` may be used to reset the form after errors.

<!-- Reset the entire form on success -->
<Form action="/users" method="post" resetOnError>
  <input type="text" name="name" />
  <input type="email" name="email" />
  <button type="submit">Submit</button>
</Form>

<!-- Reset specific fields on success -->
<Form action="/users" method="post" :resetOnError="['name']">
  <input type="text" name="name" />
  <input type="email" name="email" />
  <button type="submit">Submit</button>
</Form>

Setting new default values

The Form component provides the setDefaultsOnSuccess attribute to set the current form values as the new defaults after a successful submission.

<Form action="/users" method="post" setDefaultsOnSuccess>
  <input type="text" name="name" />
  <input type="email" name="email" />
  <button type="submit">Submit</button>
</Form>

Dotted key notation

The <Form> component supports dotted key notation for creating nested objects from flat input names. This provides a convenient way to structure form data.

<Form action="/users" method="post">
  <input type="text" name="user.name" />
  <input type="text" name="user.skills[]" />
  <input type="text" name="address.street" />
  <button type="submit">Submit</button>
</Form>

The example above would generate the following data structure.

{
  "user": {
    "name": "John Doe",
    "skills": ["JavaScript"]
  },
  "address": {
    "street": "123 Main St"
  }
}

If you need literal dots in your field names (not as nested object separators), you can escape them using backslashes.

<Form action="/config" method="post">
  <input type="text" name="app\.name" />
  <input type="text" name="settings.theme\.mode" />
  <button type="submit">Save</button>
</Form>

The example above would generate the following data structure.

{
  "app.name": "My Application",
  "settings": {
    "theme.mode": "dark"
  }
}

Programmatic access

You can access the form's methods programmatically using refs. This provides an alternative to slot props when you need to trigger form actions from outside the form.

<script setup>
import { ref } from 'vue'
import { Form } from '@inertiajs/vue3'

const formRef = ref()

const handleSubmit = () => {
  formRef.value.submit()
}
</script>

<template>
  <Form ref="formRef" action="/users" method="post">
    <input type="text" name="name" />
    <button type="submit">Submit</button>
  </Form>

  <button @click="handleSubmit">Submit Programmatically</button>
</template>

In React and Vue, refs provide access to all form methods and reactive state. In Svelte, refs expose only methods, so reactive state like isDirty and errors should be accessed via slot props instead.

Form helper

In addition to the <Form> component, Inertia also provides a useForm helper for when you need programmatic control over your form's data and submission behavior.

<script setup>
import { useForm } from '@inertiajs/vue3'

const form = useForm({
  email: null,
  password: null,
  remember: false,
})
</script>

<template>
  <form @submit.prevent="form.post('/login')">
    <!-- email -->
    <input type="text" v-model="form.email">
    <div v-if="form.errors.email">{{ form.errors.email }}</div>
    <!-- password -->
    <input type="password" v-model="form.password">
    <div v-if="form.errors.password">{{ form.errors.password }}</div>
    <!-- remember me -->
    <input type="checkbox" v-model="form.remember"> Remember Me
    <!-- submit -->
    <button type="submit" :disabled="form.processing">Login</button>
  </form>
</template>

To submit the form, you may use the get, post, put, patch and delete methods.

form.submit(method, url, options)
form.get(url, options)
form.post(url, options)
form.put(url, options)
form.patch(url, options)
form.delete(url, options)

The submit methods support all of the typical visit options, such as preserveState, preserveScroll, and event callbacks, which can be helpful for performing tasks on successful form submissions. For example, you might use the onSuccess callback to reset inputs to their original state.

form.post('/profile', {
  preserveScroll: true,
  onSuccess: () => form.reset('password'),
})

If you need to modify the form data before it's sent to the server, you can do so via the transform() method.

form
  .transform((data) => ({
    ...data,
    remember: data.remember ? 'on' : '',
  }))
  .post('/login')

You can use the processing property to track if a form is currently being submitted. This can be helpful for preventing double form submissions by disabling the submit button.

<button type="submit" :disabled="form.processing">Submit</button>

If your form is uploading files, the current progress event is available via the progress property, allowing you to easily display the upload progress.

<progress v-if="form.progress" :value="form.progress.percentage" max="100">
  {{ form.progress.percentage }}%
</progress>

Form errors

If there are form validation errors, they are available via the errors property. When building Laravel powered Inertia applications, form errors will automatically be populated when your application throws instances of ValidationException, such as when using $request->validate().

<div v-if="form.errors.email">{{ form.errors.email }}</div>
For a more thorough discussion of form validation and errors, please consult the validation documentation.

To determine if a form has any errors, you may use the hasErrors property. To clear form errors, use the clearErrors() method.

// Clear all errors...
form.clearErrors()

// Clear errors for specific fields...
form.clearErrors('field', 'anotherfield')

If you're using client-side input validation libraries or do client-side validation manually, you can set your own errors on the form using the setErrors() method.

// Set a single error...
form.setError('field', 'Your error message.');

// Set multiple errors at once...
form.setError({
  foo: 'Your error message for the foo field.',
  bar: 'Some other error for the bar field.'
});
Unlike an actual form submission, the page's props remain unchanged when manually setting errors on a form instance.

When a form has been successfully submitted, the wasSuccessful property will be true. In addition to this, forms have a recentlySuccessful property, which will be set to true for two seconds after a successful form submission. This property can be utilized to show temporary success messages.

Resetting the Form

To reset the form's values back to their default values, you can use the reset() method.

// Reset the form...
form.reset()

// Reset specific fields...
form.reset('field', 'anotherfield')

Sometimes, you may want to restore your form fields to their default values and clear any validation errors at the same time. Instead of calling reset() and clearErrors() separately, you can use the resetAndClearErrors() method, which combines both actions into a single call.

// Reset the form and clear all errors...
form.resetAndClearErrors()

// Reset specific fields and clear their errors...
form.resetAndClearErrors('field', 'anotherfield')

Setting new default values

If your form's default values become outdated, you can use the defaults() method to update them. Then, the form will be reset to the correct values the next time the reset() method is invoked.

// Set the form's current values as the new defaults...
form.defaults()

// Update the default value of a single field...
form.defaults('email', 'updated-default@example.com')

// Update the default value of multiple fields...
form.defaults({
  name: 'Updated Example',
  email: 'updated-default@example.com',
})

Form field change tracking

To determine if a form has any changes, you may use the isDirty property.

<div v-if="form.isDirty">There are unsaved form changes.</div>

Canceling Form submissions

To cancel a form submission, use the cancel() method.

form.cancel()

Form data and history state

To instruct Inertia to store a form's data and errors in history state, you can provide a unique form key as the first argument when instantiating your form.

import { useForm } from '@inertiajs/vue3'

const form = useForm('CreateUser', data)
const form = useForm(`EditUser:${user.id}`, data)

Wayfinder

Requires Inertia >= 2.0.6

When using Wayfinder in conjunction with the form helper, you can simply pass the resulting object directly to the form.submit method. The form helper will infer the HTTP method and URL from the Wayfinder object.

import { useForm } from '@inertiajs/vue3'
import { store } from 'App/Http/Controllers/UserController'

const form = useForm({
  name: 'John Doe',
  email: 'john.doe@example.com',
})

form.submit(store())

Server-side responses

When using Inertia, you don't typically inspect form responses client-side like you would with traditional XHR/fetch requests. Instead, your server-side route or controller issues a redirect response after processing the form, often redirecting to a success page.

class UsersController extends Controller
{
    public function index()
    {
        return Inertia::render('Users/Index', [
          'users' => User::all(),
        ]);
    }

    public function store(Request $request)
    {
        User::create($request->validate([
          'first_name' => ['required', 'max:50'],
          'last_name' => ['required', 'max:50'],
          'email' => ['required', 'max:50', 'email'],
        ]));

        return to_route('users.index');
    }
}

This redirect-based approach works with all form submission methods: the <Form> component, useForm helper, and manual router submissions. It makes handling Inertia forms feel very similar to classic server-side form submissions.

Server-side validation

Both the <Form> component and useForm helper automatically handle server-side validation errors. When your server returns validation errors, they're automatically available in the errors object without any additional configuration.

Unlike traditional XHR/fetch requests where you might check for a 422 status code, Inertia handles validation errors as part of its redirect-based flow, just like classic server-side form submissions, but without the full page reload.

For a complete guide on validation error handling, including error bags and advanced scenarios, see the validation documentation.

Manual form submissions

It's also possible to submit forms manually using Inertia's router methods directly, without using the <Form> component or useForm helper:

<script setup>
import { reactive } from 'vue'
import { router } from '@inertiajs/vue3'

const form = reactive({
  first_name: null,
  last_name: null,
  email: null,
})

function submit() {
  router.post('/users', form)
}
</script>

<template>
  <form @submit.prevent="submit">
    <label for="first_name">First name:</label>
    <input id="first_name" v-model="form.first_name" />
    <label for="last_name">Last name:</label>
    <input id="last_name" v-model="form.last_name" />
    <label for="email">Email:</label>
    <input id="email" v-model="form.email" />
    <button type="submit">Submit</button>
  </form>
</template>

File uploads

When making requests or form submissions that include files, Inertia will automatically convert the request data into a FormData object. This works with the <Form> component, useForm helper, and manual router submissions.

For more information on file uploads, including progress tracking, see the file uploads documentation.

XHR / fetch submissions

Using Inertia to submit forms works great for the vast majority of situations. However, in the event that you need more control over the form submission, you're free to make plain XHR or fetch requests instead, using the library of your choice.