From 528e9e11a446dceda4af7e1dabba9a9b4583d917 Mon Sep 17 00:00:00 2001 From: Pascal Baljet Date: Wed, 26 Nov 2025 16:58:47 +0100 Subject: [PATCH 1/6] wip --- v2/core-concepts/the-protocol.mdx | 17 + v2/the-basics/forms.mdx | 726 ++++++++++++++++++++++++++++++ 2 files changed, 743 insertions(+) diff --git a/v2/core-concepts/the-protocol.mdx b/v2/core-concepts/the-protocol.mdx index ac44715..bf01c7f 100644 --- a/v2/core-concepts/the-protocol.mdx +++ b/v2/core-concepts/the-protocol.mdx @@ -143,6 +143,16 @@ The following headers are automatically sent by Inertia when making requests. Yo Indicates whether the requested data should be appended or prepended when using [Infinite scroll](/v2/data-props/infinite-scroll). +The following headers are used for [Precognition](/v2/the-basics/forms#precognition) validation requests. + + + Set to `true` to indicate this is a Precognition validation request. + + + + Comma-separated list of field names to validate. + + ## Response Headers The following headers should be sent by your server-side adapter in Inertia responses. If you're using an official server-side adapter, these are handled automatically. @@ -442,3 +452,10 @@ Inertia uses specific HTTP status codes to handle different scenarios. | **302 Found** | Standard redirect response. Inertia's server-side adapters automatically convert this to `303 See Other` when returned after `PUT`, `PATCH`, or `DELETE` requests. | | **303 See Other** | Used for redirects after non-GET requests. This status code tells the browser to make a `GET` request to the redirect URL, preventing duplicate form submissions that could occur if the browser repeated the original request method. | | **409 Conflict** | Returned when there's an asset version mismatch or for external redirects. For asset mismatches, this prompts a full page reload. For external redirects, the response includes an `X-Inertia-Location` header and triggers a `window.location` redirect client-side. | + +The following status codes are used for [Precognition](/v2/the-basics/forms#precognition) validation requests. + +| Status Code | Description | +|:------------------------------|:------------------------------------------------------------------------------------------------| +| **204 No Content** | Successful Precognition validation request with no validation errors. | +| **422 Unprocessable Entity** | Precognition validation request with validation errors. The response body contains the errors. | diff --git a/v2/the-basics/forms.mdx b/v2/the-basics/forms.mdx index d906887..39aecde 100644 --- a/v2/the-basics/forms.mdx +++ b/v2/the-basics/forms.mdx @@ -954,6 +954,427 @@ function handleSubmit() { 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](#slot-props) instead. +### Precognition + +The `
` component includes built-in support for [Laravel Precognition](https://laravel.com/docs/precognition), enabling real-time form validation without duplicating your server-side validation rules on the client. + + +Precognition requires server-side support. Laravel users should see the [Laravel Precognition documentation](https://laravel.com/docs/precognition) for setup instructions. For other frameworks, see the [protocol page](/v2/core-concepts/the-protocol#request-headers) for implementation details. + + +Once your server is configured, call `validate()` with a field name to trigger validation for that field. The `invalid()` helper checks if a field has validation errors, while `validating` indicates when a request is in progress. + + + +```vue Vue icon="vuejs" + +``` + +```jsx React icon="react" + + {({ errors, invalid, validate, validating }) => ( + <> + + validate('name')} /> + {invalid('name') &&

{errors.name}

} + + + validate('email')} /> + {invalid('email') &&

{errors.email}

} + + {validating &&

Validating...

} + + + + )} + +``` + +```svelte Svelte 4 icon="s" +
+ + validate('name')} /> + {#if invalid('name')} +

{errors.name}

+ {/if} + + + validate('email')} /> + {#if invalid('email')} +

{errors.email}

+ {/if} + + {#if validating} +

Validating...

+ {/if} + + +
+``` + +```svelte Svelte 5 icon="s" +
+ {#snippet children({ errors, invalid, validate, validating })} + + validate('name')} /> + {#if invalid('name')} +

{errors.name}

+ {/if} + + + validate('email')} /> + {#if invalid('email')} +

{errors.email}

+ {/if} + + {#if validating} +

Validating...

+ {/if} + + + {/snippet} +
+``` + +
+ +You may also use the `valid()` helper to check if a field has passed validation. + + + +```vue Vue icon="vuejs" +
+ +

Valid email address

+

{{ errors.email }}

+
+``` + +```jsx React icon="react" +
+ {({ errors, invalid, valid, validate }) => ( + <> + validate('email')} /> + {valid('email') &&

Valid email address

} + {invalid('email') &&

{errors.email}

} + + )} +
+``` + +```svelte Svelte 4 icon="s" +
+ validate('email')} /> + {#if valid('email')} +

Valid email address

+ {/if} + {#if invalid('email')} +

{errors.email}

+ {/if} +
+``` + +```svelte Svelte 5 icon="s" +
+ {#snippet children({ errors, invalid, valid, validate })} + validate('email')} /> + {#if valid('email')} +

Valid email address

+ {/if} + {#if invalid('email')} +

{errors.email}

+ {/if} + {/snippet} +
+``` + +
+ + +A form input will only appear as valid or invalid once it has changed and a validation response has been received. + + +#### Validating Multiple Fields + +You may validate multiple fields at once using the `only` option. This is particularly useful when building wizard-style forms where you want to validate all visible fields before proceeding to the next step. + + + +```vue Vue icon="vuejs" +
+ + + + + +
+``` + +```jsx React icon="react" +
+ {({ validate }) => ( + <> + {/* Step 1 fields */} + + + + + + )} +
+``` + +```svelte Svelte 4 icon="s" +
+ + + + + +
+``` + +```svelte Svelte 5 icon="s" +
+ {#snippet children({ validate })} + + + + + + {/snippet} +
+``` + +
+ +#### Touch and Validate + +The `touch()` method marks fields as "touched" without triggering validation. You may then validate all touched fields by calling `validate()` without arguments. + + + +```vue Vue icon="vuejs" +
+ + + + + + +

Name has been touched

+
+``` + +```jsx React icon="react" +
+ {({ validate, touch, touched }) => ( + <> + touch('name')} /> + touch('email')} /> + touch('phone')} /> + + + + {touched('name') &&

Name has been touched

} + + )} +
+``` + +```svelte Svelte 4 icon="s" +
+ touch('name')} /> + touch('email')} /> + touch('phone')} /> + + + + {#if touched('name')} +

Name has been touched

+ {/if} +
+``` + +```svelte Svelte 5 icon="s" +
+ {#snippet children({ validate, touch, touched })} + touch('name')} /> + touch('email')} /> + touch('phone')} /> + + + + {#if touched('name')} +

Name has been touched

+ {/if} + {/snippet} +
+``` + +
+ +The `touched()` helper may also be called without arguments to check if any field has been touched. The `reset()` method clears the touched state for reset fields. + +#### Options + +The `validate()` method accepts an options object with callbacks and configuration. + +```js +validate('username', { + onSuccess: () => { + // Validation passed... + }, + onValidationError: (response) => { + // Validation failed (422 response)... + }, + onBeforeValidation: (newRequest, oldRequest) => { + // Return false to prevent validation... + }, + onFinish: () => { + // Always runs after validation... + }, +}) +``` + +You may also call `validate()` with only an options object to validate specific fields. + +```js +validate({ + only: ['name', 'email'], + onSuccess: () => goToNextStep(), +}) +``` + +Validation requests are automatically debounced. The first request fires immediately, then subsequent changes are debounced (1500ms by default). You may customize this timeout. + + + +```vue Vue icon="vuejs" +
+ +
+``` + +```jsx React icon="react" +
+ {/* ... */} +
+``` + +```svelte Svelte icon="s" +
+ +
+``` + +
+ +By default, files are excluded from validation requests to avoid unnecessary uploads. You may enable file validation when you need to validate file inputs like size or mime type. + + + +```vue Vue icon="vuejs" +
+ +
+``` + +```jsx React icon="react" +
+ {/* ... */} +
+``` + +```svelte Svelte icon="s" +
+ +
+``` + +
+ +By default, validation errors are simplified to strings (the first error message). You may keep errors as arrays to display all error messages for fields with multiple validation rules. + + + +```vue Vue icon="vuejs" +
+ +
+``` + +```jsx React icon="react" +
+ {/* ... */} +
+``` + +```svelte Svelte icon="s" +
+ +
+``` + +
+ ## Form Helper In addition to the `
` component, Inertia also provides a `useForm` helper for when you need programmatic control over your form's data and submission behavior. @@ -1561,6 +1982,311 @@ form.submit(store()) +### Precognition + +Just like the `` component, the `useForm` helper supports [Precognition](#precognition) for real-time validation. You may enable it by chaining the `withPrecognition()` method with the HTTP method and endpoint for validation requests. + + + +```js Vue icon="vuejs" +import { useForm } from '@inertiajs/vue3' + +const form = useForm({ + name: '', + email: '', +}).withPrecognition('post', '/users') +``` + +```js React icon="react" +import { useForm } from '@inertiajs/react' + +const form = useForm({ + name: '', + email: '', +}).withPrecognition('post', '/users') +``` + +```js Svelte icon="s" +import { useForm } from '@inertiajs/svelte' + +const form = useForm({ + name: '', + email: '', +}).withPrecognition('post', '/users') +``` + + + +For backwards compatibility with the `laravel-precognition` packages, you may also pass the method and URL as the first arguments to `useForm()`. + +```js +const form = useForm('post', '/users', { + name: '', + email: '', +}) +``` + +You may also use [Wayfinder](https://github.com/laravel/wayfinder) when enabling Precognition. + +```js +import { store } from 'App/Http/Controllers/UserController' + +const form = useForm({ + name: '', + email: '', +}).withPrecognition(store()) + +// Or passing Wayfinder as the first argument... +const form = useForm(store(), { + name: '', + email: '', +}) +``` + +Once Precognition is enabled, call `validate()` with a field name to trigger validation for that field. The `invalid()` helper checks if a field has validation errors, while `validating` indicates when a request is in progress. + + + +```vue Vue icon="vuejs" + + + +``` + +```jsx React icon="react" +import { useForm } from '@inertiajs/react' + +const { data, setData, post, errors, validating, validate, invalid } = useForm('post', '/users', { + name: '', + email: '', +}) + +function submit(e) { + e.preventDefault() + post('/users') +} + +return ( + + setData('name', e.target.value)} onBlur={() => validate('name')} /> + {invalid('name') &&

{errors.name}

} + + setData('email', e.target.value)} onBlur={() => validate('email')} /> + {invalid('email') &&

{errors.email}

} + + {validating &&

Validating...

} + + + +) +``` + +```svelte Svelte 4 icon="s" + + +
$form.post('/users')}> + $form.validate('name')} /> + {#if $form.invalid('name')} +

{$form.errors.name}

+ {/if} + + $form.validate('email')} /> + {#if $form.invalid('email')} +

{$form.errors.email}

+ {/if} + + {#if $form.validating} +

Validating...

+ {/if} + + +
+``` + +```svelte Svelte 5 icon="s" + + +
{ e.preventDefault(); $form.post('/users') }}> + $form.validate('name')} /> + {#if $form.invalid('name')} +

{$form.errors.name}

+ {/if} + + $form.validate('email')} /> + {#if $form.invalid('email')} +

{$form.errors.email}

+ {/if} + + {#if $form.validating} +

Validating...

+ {/if} + + +
+``` + +
+ +You may also use the `valid()` helper to check if a field has passed validation. + +#### Touch and Validate + +The `touch()` method marks fields as "touched" without triggering validation. You may then validate all touched fields by calling `validate()` without arguments. The `touched()` helper checks if a field has been touched. The `reset()` method clears the touched state for reset fields. + + + +```vue Vue icon="vuejs" + + + + + +

Name has been touched

+``` + +```jsx React icon="react" + setData('name', e.target.value)} onBlur={() => touch('name')} /> + setData('email', e.target.value)} onBlur={() => touch('email')} /> + + + +{touched('name') &&

Name has been touched

} +``` + +```svelte Svelte icon="s" + $form.touch('name')} /> + $form.touch('email')} /> + + + +{#if $form.touched('name')} +

Name has been touched

+{/if} +``` + +
+ +#### Options + +Validation requests are automatically debounced (1500ms by default). You may customize this timeout using `setValidationTimeout()`. + + + +```js Vue icon="vuejs" +const form = useForm('post', '/users', { + name: '', +}).setValidationTimeout(500) +``` + +```js React icon="react" +const form = useForm('post', '/users', { + name: '', +}) + +form.setValidationTimeout(500) +``` + +```js Svelte icon="s" +const form = useForm('post', '/users', { + name: '', +}) + +$form.setValidationTimeout(500) +``` + + + +By default, files are excluded from validation requests to avoid unnecessary uploads. You may enable file validation using `validateFiles()`. + + + +```js Vue icon="vuejs" +const form = useForm('post', '/users', { + avatar: null, +}).validateFiles() +``` + +```js React icon="react" +const form = useForm('post', '/users', { + avatar: null, +}) + +form.validateFiles() +``` + +```js Svelte icon="s" +const form = useForm('post', '/users', { + avatar: null, +}) + +$form.validateFiles() +``` + + + +By default, validation errors are simplified to strings (the first error message). You may keep errors as arrays using `withAllErrors()`. + + + +```js Vue icon="vuejs" +const form = useForm('post', '/users', { + name: '', +}).withAllErrors() +``` + +```js React icon="react" +const form = useForm('post', '/users', { + name: '', +}) + +form.withAllErrors() +``` + +```js Svelte icon="s" +const form = useForm('post', '/users', { + name: '', +}) + +$form.withAllErrors() +``` + + + +With Precognition enabled, you may call `submit()` without arguments to submit to the configured endpoint. + ## 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](/v2/the-basics/redirects) response after processing the form, often redirecting to a success page. From e3399254cf8c968f9d078251a908ed36a85390fb Mon Sep 17 00:00:00 2001 From: Pascal Baljet Date: Fri, 28 Nov 2025 07:51:49 +0100 Subject: [PATCH 2/6] Update the-protocol.mdx --- v2/core-concepts/the-protocol.mdx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/v2/core-concepts/the-protocol.mdx b/v2/core-concepts/the-protocol.mdx index bf01c7f..5e7fe06 100644 --- a/v2/core-concepts/the-protocol.mdx +++ b/v2/core-concepts/the-protocol.mdx @@ -169,6 +169,20 @@ The following headers should be sent by your server-side adapter in Inertia resp Set to `X-Inertia` to help browsers correctly differentiate between HTML and JSON responses. +The following headers are used for [Precognition](/v2/the-basics/forms#precognition) validation responses. + + + Set to `true` to indicate this is a Precognition validation response. + + + + Set to `true` when validation passes with no errors, combined with a `204 No Content` status code. + + + + Set to `Precognition` on all responses when the Precognition middleware is applied. + + ## The Page Object Inertia shares data between the server and client via a page object. This object includes the necessary information required to render the page component, update the browser's history state, and track the site's asset version. The page object can include the following properties: From f40340eb331b3c3b1127fad4b775314f50923d7e Mon Sep 17 00:00:00 2001 From: Pascal Baljet Date: Fri, 28 Nov 2025 08:15:39 +0100 Subject: [PATCH 3/6] Update forms.mdx --- v2/the-basics/forms.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2/the-basics/forms.mdx b/v2/the-basics/forms.mdx index 39aecde..33602a9 100644 --- a/v2/the-basics/forms.mdx +++ b/v2/the-basics/forms.mdx @@ -2201,7 +2201,7 @@ The `touch()` method marks fields as "touched" without triggering validation. Yo #### Options -Validation requests are automatically debounced (1500ms by default). You may customize this timeout using `setValidationTimeout()`. +Validation requests are automatically debounced. The first request fires immediately, then subsequent changes are debounced (1500ms by default). You may customize this timeout using `setValidationTimeout()`. From e8a9320f6990c72ff4bffb963e8adad4facfe5f1 Mon Sep 17 00:00:00 2001 From: Pascal Baljet Date: Fri, 28 Nov 2025 09:28:59 +0100 Subject: [PATCH 4/6] Update forms.mdx --- v2/the-basics/forms.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/v2/the-basics/forms.mdx b/v2/the-basics/forms.mdx index 33602a9..eea7d99 100644 --- a/v2/the-basics/forms.mdx +++ b/v2/the-basics/forms.mdx @@ -1308,19 +1308,19 @@ Validation requests are automatically debounced. The first request fires immedia ```vue Vue icon="vuejs" -
+
``` ```jsx React icon="react" -
+ {/* ... */}
``` ```svelte Svelte icon="s" -
+
``` From 93452828e3ad2a113beab4fcb2329f55c785f8e1 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Sat, 6 Dec 2025 14:23:03 -0600 Subject: [PATCH 5/6] Update forms.mdx --- v2/the-basics/forms.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2/the-basics/forms.mdx b/v2/the-basics/forms.mdx index eea7d99..621e089 100644 --- a/v2/the-basics/forms.mdx +++ b/v2/the-basics/forms.mdx @@ -2257,7 +2257,7 @@ $form.validateFiles()
-By default, validation errors are simplified to strings (the first error message). You may keep errors as arrays using `withAllErrors()`. +By default, validation errors are simplified to strings (the first error message). You can indicate you would like all errors as arrays using `withAllErrors()`. From 2e80598e39ef99c2fccad92e884650ac9eda971f Mon Sep 17 00:00:00 2001 From: Pascal Baljet Date: Wed, 10 Dec 2025 17:25:21 +0100 Subject: [PATCH 6/6] Update forms.mdx --- v2/the-basics/forms.mdx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/v2/the-basics/forms.mdx b/v2/the-basics/forms.mdx index 621e089..7c99275 100644 --- a/v2/the-basics/forms.mdx +++ b/v2/the-basics/forms.mdx @@ -956,6 +956,8 @@ In React and Vue, refs provide access to all form methods and reactive state. In ### Precognition +v2.3+ + The `
` component includes built-in support for [Laravel Precognition](https://laravel.com/docs/precognition), enabling real-time form validation without duplicating your server-side validation rules on the client. @@ -1984,8 +1986,14 @@ form.submit(store()) ### Precognition +v2.3+ + Just like the `` component, the `useForm` helper supports [Precognition](#precognition) for real-time validation. You may enable it by chaining the `withPrecognition()` method with the HTTP method and endpoint for validation requests. + +Precognition requires server-side support. Laravel users should see the [Laravel Precognition documentation](https://laravel.com/docs/precognition) for setup instructions. For other frameworks, see the [protocol page](/v2/core-concepts/the-protocol#request-headers) for implementation details. + + ```js Vue icon="vuejs" @@ -2026,6 +2034,10 @@ const form = useForm('post', '/users', { }) ``` + +Since Precognition is now built-in, you may remove the `laravel-precognition` package and import `useForm` from your Inertia adapter instead. + + You may also use [Wayfinder](https://github.com/laravel/wayfinder) when enabling Precognition. ```js