Laravel Precog Always Returns Success: Troubleshooting Guide
Debug why Laravel Precog's HandlePrecognitiveRequests middleware always returns success. Common causes and fixes for precognitive request validation.
Laravel Precognition is a powerful feature that lets you validate forms before the user submits them, providing real-time validation feedback. But a common issue reported by developers — including a recent Laracasts thread — is that Precognition always returns success, even when the input is invalid. The HandlePrecognitiveRequests middleware seems to do nothing. Here is why and how to fix it.
How Precognition Works
Precognition works by sending a HEAD or POST request with a special X-Precognition header. Laravel detects this header and runs validation without executing the full controller logic. If validation fails, it returns a JSON response with the validation errors. If it passes, it returns a 204 No Content response.
The key middleware is HandlePrecognitiveRequests, which intercepts precognitive requests and returns early with validation results.
The Problem: Precognition Always Returns 200/201
Here is a typical setup that looks correct but silently bypasses Precognition:
<?php
// routes/web.php
use App\Http\Requests\EmailRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
use Illuminate\Support\Facades\Route;
Route::post('/', function (EmailRequest $request) {
// ... process the request
})->middleware(HandlePrecognitiveRequests::class);
You send a request with the Precognition: true header and invalid data, expecting validation errors. But instead, the request goes through the full controller and returns a 200 or 201 response.
Common Causes and Fixes
1. Missing the X-Precognition Header
This is the most common cause. Precognition requires the Precognition: true header (or X-Precognition depending on your version). Without it, the middleware passes the request through as normal:
// Frontend — WRONG (missing the header)
fetch('/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email: 'invalid' }),
});
// Frontend — CORRECT
fetch('/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Precognition': 'true', // OR 'X-Precognition': 'true'
},
body: JSON.stringify({ email: 'invalid' }),
}); 2. Form Request Not Using ValidatesWhenResolved
The HandlePrecognitiveRequests middleware relies on your form request using the ValidatesWhenResolved trait (which it does by default in recent Laravel versions). But if you override the validate method or use manual validation in the controller, Precognition will not work:
<?php
// routes/web.php — WRONG: manual validation bypasses Precognition
Route::post('/', function (Request $request) {
$validated = $request->validate([
'email' => 'required|email',
]);
// ... process
})->middleware(HandlePrecognitiveRequests::class); <?php
// routes/web.php — CORRECT: use a Form Request
Route::post('/', function (EmailRequest $request) {
// Form request handles validation automatically
// ... process
})->middleware(HandlePrecognitiveRequests::class); 3. Middleware Order Matters
The HandlePrecognitiveRequests middleware must run before your form request is resolved. If you have other middleware on the route that runs first and calls $request->validate() manually, Precognition will be bypassed:
<?php
// Kernel.php or route — WRONG
Route::post('/', function (EmailRequest $request) {
// ...
})->middleware(['throttle:api', HandlePrecognitiveRequests::class]);
If throttle or any other middleware calls $request->validate(), the form request validation in the controller will not trigger precognitive behavior. Move HandlePrecognitiveRequests earlier in the pipeline, or remove other middleware that interferes.
4. Inertia.js Configuration
If you are using Inertia.js with Precognition, the frontend setup must use the @inertiajs/inertia-precognition plugin or include the header automatically. Here is the correct Inertia setup:
<script>
import { createInertiaApp } from '@inertiajs/vue3';
import { precognitive } from '@inertiajs/vue3-precognition';
createInertiaApp({
// ...
plugins: [
precognitive(),
],
});
</script> <!-- Vue component -->
<template>
<form @submit="submit">
<input
v-model="form.email"
@change="form.validate('email')"
/>
<div v-if="form.invalid('email')">
{{ form.errors.email }}
</div>
</form>
</template>
<script setup>
import { usePrecognition } from '@inertiajs/vue3-precognition';
const form = usePrecognition('post', '/', {
email: '',
});
function submit() {
form.submit();
}
</script> 5. Testing Precognition
If you want to test whether Precognition is working, use Laravel's HTTP test helpers with the precognitive header:
<?php
test('precognition validates email field', function () {
$response = $this->post('/', [
'email' => 'not-an-email',
], [
'Precognition' => 'true',
]);
$response
->assertUnprocessable()
->assertJsonValidationErrors(['email']);
}); Quick Debugging Checklist
- Open the browser's Network tab and check: is the
Precognition: trueheader being sent? - Are you using a Form Request class (not
$request->validate())? - Is
HandlePrecognitiveRequeststhe first middleware on the route? Check withphp artisan route:list -v - If using Inertia, have you installed and configured the precognition plugin?
- Does the response include
X-Precognition: successheader when validation passes?
Conclusion
Laravel Precognition silently falls through to normal request handling when it is not configured correctly. The three most common culprits are: not sending the Precognition header, using manual validation instead of a Form Request, or incorrect middleware ordering. Check those first, and Precognition will start returning proper validation errors as expected.
Stefan
SEO engineer and Laravel developer. Building tools to help Laravel applications rank higher in search results.