Laravel Validation¶
Laravel provides built-in request validation with 90+ rules, automatic redirect-back on failure, and flash error messages. Validation can be inline in controllers or extracted to Form Request classes. Validated data is safe for [[laravel-eloquent-orm]] operations. Errors are displayed in [[laravel-blade-templates]] via $errors and @error.
Key Facts¶
$request->validate([...])returns validated data array; auto-redirects back with errors on failure- Validation rules as string (
'required|email|max:255') or array (['required', 'email', 'max:255']) $errorsvariable is always available in Blade templates (even when empty)old('field')retrieves previous input after validation failure- Form Request classes: separate validation logic into dedicated
Requestsubclass php artisan make:request StorePostRequestgenerates a Form Request class- Custom error messages via third parameter or Form Request
messages()method - Localization: validation messages in
lang/en/validation.php uniquerule:'email' => 'unique:users,email'checks against databasesometimesrule: only validate if field is present in request
Patterns¶
Inline validation in controller¶
<?php
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'slug' => 'required|string|unique:posts,slug',
'body' => 'required|string',
'category_id' => 'required|exists:categories,id',
'image' => 'nullable|image|mimes:jpg,png,webp|max:2048',
'tags' => 'nullable|array',
'tags.*' => 'exists:tags,id',
]);
Post::create($validated);
return redirect()->route('posts.index')->with('success', 'Post created.');
}
// On validation failure: auto redirect back with $errors and old input
Form Request class¶
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return true; // or check user permissions
}
public function rules(): array
{
return [
'title' => 'required|string|max:255',
'slug' => 'required|string|unique:posts,slug',
'body' => 'required|string',
'category_id' => 'required|exists:categories,id',
];
}
public function messages(): array
{
return [
'title.required' => 'Post title is required.',
'slug.unique' => 'This URL slug is already taken.',
'category_id.exists' => 'Selected category does not exist.',
];
}
}
<?php
// Controller uses type-hinted Form Request
public function store(StorePostRequest $request)
{
// Validation already passed at this point
Post::create($request->validated());
return redirect()->route('posts.index');
}
Common validation rules¶
<?php
$rules = [
// String rules
'name' => 'required|string|min:2|max:100',
'email' => 'required|email',
'slug' => 'required|alpha_dash', // letters, numbers, dashes, underscores
'url' => 'nullable|url',
// Numeric rules
'price' => 'required|numeric|min:0',
'quantity' => 'required|integer|between:1,100',
// Date rules
'start' => 'required|date|after:today',
'end' => 'required|date|after:start',
// File rules
'avatar' => 'nullable|image|mimes:jpg,png|max:2048', // max 2MB
'document' => 'nullable|file|mimes:pdf,doc|max:10240',
// Database rules
'email' => 'required|unique:users,email', // unique in table
'email' => 'required|unique:users,email,' . $user->id, // ignore current record
'cat_id' => 'required|exists:categories,id', // must exist in table
// Array rules
'tags' => 'nullable|array',
'tags.*' => 'integer|exists:tags,id', // each element
// Conditional rules
'password' => 'required|confirmed|min:8', // requires password_confirmation field
'bio' => 'sometimes|string|max:500', // only validate if present
];
Update validation (unique except current)¶
<?php
// When updating, exclude current record from unique check
public function update(Request $request, Post $post)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'slug' => 'required|unique:posts,slug,' . $post->id,
'body' => 'required|string',
]);
$post->update($validated);
return redirect()->route('posts.index');
}
Login validation (security considerations)¶
<?php
public function authenticate(Request $request)
{
// For LOGIN: only validate format, NOT password strength
// Revealing password requirements helps attackers
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'], // NO min length, NO complexity rules
]);
if (Auth::attempt($credentials)) {
$request->session()->regenerate();
return redirect()->intended('/admin');
}
// Generic error message - don't reveal which field was wrong
return back()->with('error', 'Incorrect email or password.');
}
Gotchas¶
| Symptom | Cause | Fix |
|---|---|---|
old() shows nothing after error | Input missing name attribute | Add name="field" to HTML input |
| Validation passes for empty optional field | nullable rule allows null/empty | This is correct behavior; add required if field is mandatory |
| File validation accepts wrong type | Missing mimes rule or wrong mime type | Add mimes:jpg,png,pdf explicitly |
unique fails on update (own record) | Current record ID not excluded | Use unique:table,column,' . $model->id |
| Revealing password requirements on login | Validating password rules on login form | Only use required for login; full rules for registration |
Form Request authorize() returns false | Default is false | Set to true or add authorization logic |
| Array validation not working | Missing .* suffix for array elements | Use 'tags.*' => 'exists:tags,id' |
See Also¶
- [[laravel-blade-templates]] - displaying errors with @error and $errors
- [[laravel-authentication]] - login form validation
- [[laravel-eloquent-orm]] - using validated data for CRUD
- https://laravel.com/docs/11.x/validation
- https://laravel.com/docs/11.x/validation#available-validation-rules