How does Inertia 3's built-in Precognition give a customer form live validation with validate() and invalid(), and what must the Laravel route provide?
answer
- same URL, Precognition: true header
- precognitive middleware alias
- rules must live in a form request
- 204 with Precognition-Success, or 422
- debounced 1500 ms, files skipped
basics
~20 sPrecognition sends the form to its real route with a Precognition header; Laravel's precognitive middleware runs validation from the route's form request but not the controller, answering 204 or 422, and useForm's validate() and invalid() show the result per field.
solid answer
~40 sIn Inertia 3, Precognition is built into the form helper: `useForm('put', '/customers/42', data)` or `useForm(data).withPrecognition('put', url)`, and `<Form>` is always capable. `validate('email')` sends the current data to the same URL with `Precognition: true` and `Precognition-Validate-Only: email`, debounced at 1500 ms, skipping files unless `validateFiles()`. The route needs Laravel's `HandlePrecognitiveRequests` middleware (alias `precognitive`) and its rules in a **form request**: during a precognitive request Laravel runs middleware and resolves controller dependencies, which validates the form request limited to the listed fields, but never runs the controller method. Clean data answers 204 with `Precognition-Success: true`; failures come back as 422 JSON because the request accepts JSON. `invalid('email')` is true when the form's errors hold that field, `valid('email')` once it validated cleanly, and `validating` shows a request in flight.
code
php · 11 lines<?php
use App\Http\Controllers\CustomerController;
use Illuminate\Support\Facades\Route;
Route::put('/customers/{customer}', [CustomerController::class, 'update'])
->middleware('precognitive');
// CustomerController::update(UpdateCustomerRequest $request, Customer $customer)
// The form request validates while parameters resolve;
// on a precognitive request the method body never runs.go deeper
Recall that validate('field') asks the server whether a field is valid and invalid('field') shows whether it has an error.
Explain the Precognition header, the precognitive middleware, the 204 versus 422 responses, and why rules must sit in a form request.
Harden it: skip middleware side effects with isPrecognitive(), relax costly rules during precognition, tune the debounce, and decide when files need validateFiles().
Judge whether live server validation is worth the extra requests for a given form, considering server load, perceived speed and keeping one source of rules.
## What Precognition is **Laravel Precognition** is a protocol for asking the server "would this request pass validation?" without running the action. The frontend sends the real request, to the real URL, with a `Precognition: true` header; the backend runs everything up to the controller method and stops. The result is live validation that uses the exact server-side rules, with nothing duplicated in JavaScript. Since **Inertia 2.3**, and in Inertia 3, the client side is built into the adapters' form APIs, so the separate `laravel-precognition` frontend packages are no longer needed in an Inertia app (the adapters depend on Precognition's core library internally). ## Enabling it on the client With `useForm`, give the form a method and URL for validation: - `useForm('put', '/customers/42', { name, email })`, the form accepted for compatibility with the old packages; - `useForm({ name, email }).withPrecognition('put', url)`; - a Wayfinder route object in place of method and URL. The `<Form>` component always has Precognition wired to its own `action` and `method`, so its slot's `validate` works with no extra setup. The members you use: | Member | Meaning | |---|---| | `validate('email')` | validate one field now (debounced) | | `validate({ only: ['name', 'email'] })` | validate several, for wizard steps | | `touch('email')` then `validate()` | validate every touched field | | `invalid('email')` | `true` when the form's errors contain the field | | `valid('email')` | `true` once the field was validated and has no error | | `validating` | a validation request is in flight | ## What the request looks like 1. The helper sends the current data with the form's method to the form's URL. 2. Headers include `Precognition: true`, `Precognition-Validate-Only: email` and `Accept: application/json`. 3. Requests are **debounced**: the first fires at once and later ones wait 1500 ms by default (`setValidationTimeout()` or the `validationTimeout` prop change it). 4. A field is only sent once its value differs from the initial data. 5. **Files are excluded** unless you call `validateFiles()` (`validateFiles` prop on `<Form>`). 6. Errors are reduced to the first message per field unless `withAllErrors()` is called. ## What the Laravel route must provide - The **`HandlePrecognitiveRequests`** middleware, registered as the `precognitive` alias in Laravel 13: `Route::put('/customers/{customer}', ...)->middleware('precognitive')`. - Validation rules in a **form request** type-hinted on the action. During a precognitive request the middleware swaps in dispatchers that resolve the action's parameters, which is when a form request validates, and then abort with **204** and `Precognition-Success: true` without calling the method. - The form request filters its rules down to the fields named in `Precognition-Validate-Only`, so untouched fields do not report errors early. Rules written inline with `$request->validate()` inside the controller method never run during a precognitive request, because the method body never runs; every such request would come back as a success. ## Common mistakes - adding the client calls but forgetting `->middleware('precognitive')`, so the "validation" request runs the real update; - keeping rules inline in the controller method, where a precognitive request never reaches them; - expecting an avatar field to be checked live without `validateFiles()`; - calling `validate('email')` before the field changed and wondering why no request is sent. ## Responses and side effects | Outcome | Response | |---|---| | all requested fields pass | `204 No Content`, `Precognition-Success: true` | | a rule fails | `422` JSON errors, since the request accepts JSON | This is the one place an Inertia app sees a 422, and the helper handles it without navigating. Responses also carry `Precognition: true` and `Vary: Precognition`. Because **all route middleware still runs**, middleware with side effects such as counting interactions should check `$request->isPrecognitive()` and skip. Form requests can use the same check to relax expensive rules, for example running an uncompromised-password rule only on the real submission.
- Why does validation with $request->validate() inside the controller never fail precognitively?A precognitive request stops after resolving the action's parameters and answers 204; the method body, where the inline `validate()` call lives, never executes. Only a form request, which validates while it is resolved, is checked.
- How do you stop a password field from being checked against a breach list on every keystroke?In the form request's `rules()`, branch on `$this->isPrecognitive()`: send only cheap rules such as required and a minimum length during precognition, and add the expensive rule for the real submission.
saying these in an interview costs you the question
- Precognition runs the controller in a rolled-back transaction
- Inertia 3 still needs the laravel-precognition npm package
- Rules written inline with $request->validate() are checked precognitively
- Every keystroke sends a request with no debounce
- Uploaded files are validated precognitively by default