Skip to content

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'])
  • $errors variable 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 Request subclass
  • php artisan make:request StorePostRequest generates a Form Request class
  • Custom error messages via third parameter or Form Request messages() method
  • Localization: validation messages in lang/en/validation.php
  • unique rule: 'email' => 'unique:users,email' checks against database
  • sometimes rule: 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 artisan make:request StorePostRequest
<?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