skip to content

Validating Incoming Data

Checking request input in Laravel through form request classes, the rule catalogue and Validator::make(), then reporting failures as error bags or a 422 JSON body. Asked in almost every interview.

on this pageshow

explore

questions

21

In a Laravel 13 controller, what does $request->validate() return on success, and what happens when validation fails?

level: juniorimportance: must knowfreq 78%

answer

  1. a macro on the Request class
  2. returns only the validated fields
  3. throws ValidationException on failure
  4. redirect back, or 422 for JSON
  5. no $this->validate() in the empty base controller

basics

~20 s

$request->validate($rules) returns an array of just the validated fields. On failure it throws ValidationException, which becomes a redirect back with flashed errors and old input, or a 422 JSON response when the request expects JSON; the rest of the action never runs.

solid answer

~40 s

`validate()` is a macro Laravel registers on `Illuminate\Http\Request`. It builds a validator from `$request->all()` and your rules (plus optional messages and attribute names) and calls the validator's `validate()`. On success it returns `validated()` — only keys that had rules — so `$data = $request->validate([...])` is ready to persist. On failure it throws `ValidationException`, so the code after it never runs; the exception handler redirects back with the errors and old input flashed, or returns a 422 with the errors as JSON when the client expects JSON. `validateWithBag('post', $rules)` does the same but tags the errors with a named bag for pages that hold several forms. Since Laravel 11 the skeleton's base `Controller` is empty, so the older `$this->validate($request, ...)` helper is gone unless you add the `ValidatesRequests` trait yourself.

code

php · 22 lines
php
<?php

namespace App\Http\Controllers;

use App\Models\Import;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class ImportController extends Controller
{
    public function store(Request $request): RedirectResponse
    {
        $data = $request->validate([
            'name' => ['required', 'string', 'max:120'],
            'delimiter' => ['required', 'in:comma,semicolon,tab'],
        ]);

        $import = Import::create($data);

        return redirect()->route('imports.show', $import);
    }
}

go deeper

for a junior

Know that validate() returns the validated data and that failure sends the user back with errors automatically.

for a middle

Explain the macro, the thrown ValidationException, the redirect-versus-422 decision and validateWithBag().

for a senior

Know why the empty base controller broke old $this->validate() calls and when to move to a form request or Validator::make().

for a principal

Set the convention for where validation lives, inline, form request or service, so endpoints stay consistent.

## The one-line validator For a short action, the simplest validation in Laravel is inline: ```php $data = $request->validate([ 'title' => ['required', 'string', 'max:120'], 'starts_at' => ['required', 'date'], ]); ``` `validate()` is not a method written on the `Request` class itself; the framework's `FoundationServiceProvider` registers it as a **macro**. The macro: 1. Builds a validator from `$request->all()` (query string, body and files) and your rules; extra arguments are passed on as custom messages and attribute names. 2. Calls the validator's own `validate()` method. 3. Returns whatever that returns. ## On success: the validated array The validator's `validate()` returns `validated()`, which contains **only the keys that had rules** and were present. So `$data` above never includes a stray `is_featured` field that the client added, and it can be handed to `create()` directly. ## On failure: an exception, not a return value If any rule fails, `ValidationException` is thrown. Consequences: - Nothing after the `validate()` line runs; no `if` is needed. - The exception carries the validator, a default status of **422**, an error bag name (`default`) and an optional redirect target. - Laravel's exception handler decides the response: for a normal form post, a **redirect back** to the previous URL with the errors and the old input flashed to the session; for a request that expects JSON, a **422** response with the errors as JSON. - `ValidationException` is on the handler's internal "don't report" list, so failed validations do not fill your logs. ## validateWithBag() `$request->validateWithBag('login', $rules)` is identical except that, on failure, it sets the exception's error bag name. It exists for pages with several forms (log in and register side by side) so each form can show only its own errors. ## What changed in recent skeletons Older apps often called `$this->validate($request, $rules)` inside controllers. That helper comes from the `Illuminate\Foundation\Validation\ValidatesRequests` trait, which the old base controller used. Since Laravel 11 the skeleton's `App\Http\Controllers\Controller` is an **empty abstract class** — no traits — so in a Laravel 13 app that call fails with an undefined-method error. The trait still exists in the framework and can be added back, but `$request->validate()` is the idiomatic replacement. | Approach | Where | Returns | On failure | |---|---|---|---| | `$request->validate($rules)` | any code with the request | validated array | throws `ValidationException` | | `$request->validateWithBag('bag', $rules)` | same | validated array | throws, bag name set | | `$this->validate($request, $rules)` | controllers using `ValidatesRequests` | validated array | throws; not available on the empty base controller | | Form request type-hint | controller signature | the request object | throws before the action runs | ## When inline validation is enough - One or two actions, a handful of rules, no authorization logic tied to the input. - Prototypes and internal tools. - Endpoints whose rules are unlikely to be reused. Move to a form request when the rules grow, need to be shared, or need `prepareForValidation`-style hooks; move to `Validator::make()` when you need to inspect the result instead of throwing. ## Pitfalls - Using `$request->all()` after `validate()` instead of the returned array throws away the filtering. - Catching `ValidationException` in the controller to "handle" it usually re-implements what the handler already does. - Calling `validate()` twice with different rules validates twice and returns two different arrays; keep one rule set per action. - Validating route parameters: `$request->validate()` sees `$request->all()`, which does not include route segments like `{import}`; those are handled by route constraints or model binding, not by these rules. ## How to answer in an interview Three sentences cover it: it returns the validated subset; it throws `ValidationException` on failure; the handler turns that into a redirect with flashed errors or a 422 for JSON. Adding the Laravel 11 change — no `ValidatesRequests` on the base controller — shows you have worked in a current skeleton rather than an older tutorial.

  • Can you pass custom messages to $request->validate()?
    Yes. The macro accepts the rules and forwards any further arguments to the validator factory, so `$request->validate($rules, $messages, $attributes)` sets custom messages and attribute names exactly as the third and fourth arguments of `Validator::make()` would.
  • Why does validation failure not appear in the application log?
    `ValidationException` is on the exception handler's internal don't-report list, alongside exceptions like `AuthorizationException` and `ModelNotFoundException`. It is an expected outcome of user input, so it is rendered as a response but not reported.

saying these in an interview costs you the question

  • validate() returns true or false, and you check it with an if
  • validate() returns all request input once validation passes
  • A failed validate() always returns a 422, even for HTML forms
  • $this->validate() works in every Laravel 13 controller out of the box
open as a page

In a Laravel Blade view, how do you show validation errors after a failed form submission, and where does the $errors variable come from?

level: juniorimportance: must knowfreq 76%

basics

~20 s

A failed form validation redirects back and flashes the errors to the session; ShareErrorsFromSession in the web group shares them with every view as $errors, a ViewErrorBag. Read them with @error('field') and $message, or first(), has() and all().

open as a page

In Laravel, what is a form request class, and when does it authorize and validate the request relative to your controller method?

level: middleimportance: must knowfreq 75%

basics

~20 s

A form request is a class extending Illuminate\Foundation\Http\FormRequest that carries authorize() and rules(). When the container resolves it for a type-hinted controller parameter, it authorizes and validates first; the action body runs only if both pass.

open as a page

In Laravel, when would you use Validator::make() instead of $request->validate(), and how do fails(), validate() and validated() differ?

level: middleimportance: must knowfreq 62%

basics

~20 s

Validator::make($data, $rules) validates any array and does nothing on failure until you ask. fails() returns a boolean and fills errors(); validate() throws ValidationException or returns the validated array; validated() returns the validated data and also throws if invalid.

open as a page

In Laravel, where can you override a validation error message or the :attribute name, and in what order does the validator look for them?

level: middleimportance: must knowfreq 62%

basics

~20 s

Pass inline messages and attribute names (a form request's messages() and attributes(), or the extra validator arguments) or put them in lang/{locale}/validation.php under custom and attributes. Inline entries win, then custom, then the rule's default line.

open as a page

In Laravel validation, what is the difference between the nullable, sometimes and present rules, and when do you need each?

level: middleimportance: must knowfreq 65%

basics

~20 s

nullable lets a present field be null and then skips its other rules; sometimes runs a field's rules only when the key exists in the input; present requires the key to exist but accepts an empty value.

open as a page

In Laravel, how do you validate that a username is unique on a profile-edit form without rejecting the user's own current username?

level: middleimportance: must knowfreq 72%

basics

~20 s

Use Rule::unique('users', 'username')->ignore($user), passing the authenticated or route-bound model so its own row is excluded from the check. Take the ignored id from the server, never from request input, and keep a unique index in the database.

open as a page

In a Laravel controller that receives a form request, why should you persist $request->validated() rather than $request->all()?

level: seniorimportance: must knowfreq 70%

basics

~20 s

validated() returns only the input keys that had rules and passed, so extra fields a client adds never reach create() or update(). all() returns everything sent, which turns any mass-assignment gap into a way to write columns the form never offered.

open as a page

Why does a Laravel form request freshly generated by make:request answer every submission with a 403, and how do you fix it?

level: juniorimportance: should knowfreq 55%

basics

~20 s

The make:request stub's authorize() returns false, and a false result throws AuthorizationException, which Laravel renders as a 403. Return a real check (or true when authorization lives elsewhere), or delete the method, which counts as authorized.

open as a page

In Laravel validation, when must rules be written as an array instead of a pipe-delimited string, and what does the bail rule change?

level: juniorimportance: should knowfreq 45%

basics

~20 s

A pipe string is split on |, so a regex containing a pipe, a closure, or a rule object without a string form such as Password::defaults() or File::types() must go in an array. bail stops running a field's remaining rules after its first failure.

open as a page

In a Laravel form request, in what order do prepareForValidation(), authorize(), rules(), after() and passedValidation() run, and what belongs in each?

level: middleimportance: should knowfreq 48%

basics

~20 s

prepareForValidation() runs first, then authorize(), then the validator built from rules() plus after() callables, then passedValidation() only on success. Normalise input in the first, decide access in the second, cross-field checks in after(), and post-success tweaks last.

open as a page

On a manually built Laravel validator, what do the after(), sometimes() and stopOnFirstFailure() methods add, and when does each take effect?

level: middleimportance: should knowfreq 32%

basics

~20 s

after() registers callbacks that run once the rules finish, to add cross-field errors. sometimes() adds rules to fields only when a closure over the input returns true. stopOnFirstFailure() stops checking further fields after the first failure. All are set before fails() or validate().

open as a page

In Laravel, how do you raise a field-level validation error from service or domain code that has no validator rule for the check?

level: middleimportance: should knowfreq 45%

basics

~20 s

Throw ValidationException::withMessages(['field' => 'message']). It builds an empty validator, adds the messages under those keys, and is rendered like any validation failure: a redirect back with errors, or a 422 JSON response for JSON requests.

open as a page

On a Laravel page with separate login and registration forms that both have an email field, how do you keep their validation errors apart?

level: middleimportance: should knowfreq 36%

basics

~10 s

Flash one form's errors into a named bag, via validateWithBag('login', ...), a form request's $errorBag or #[ErrorBag('login')], or withErrors($validator, 'login'), then read it with $errors->login->first('email') or @error('email', 'login').

open as a page

In Laravel validation, how do required_if, exclude_if and Rule::when differ when a field depends on another field's value?

level: middleimportance: should knowfreq 42%

basics

~20 s

required_if makes a field mandatory when another field has a value but still validates and keeps it otherwise. exclude_if skips the field entirely and drops it from validated(). Rule::when picks one of two rule sets from a boolean or closure evaluated at validation time.

open as a page

Inside a queued Laravel job that imports a CSV file, how would you validate each row and report bad rows without failing the whole import?

level: seniorimportance: should knowfreq 35%

basics

~10 s

Build Validator::make($row, $rules) per row, check fails(), and record the row number with errors()->toArray() instead of throwing. Persist validated() for good rows. Throwing ValidationException would fail the job attempt rather than skip the row.

open as a page

A Laravel API endpoint answers failed validation with a 302 redirect instead of a 422 JSON body. What decides the response type, and what does the 422 contain?

level: seniorimportance: should knowfreq 52%

basics

~20 s

The exception handler renders JSON when the shouldRenderJsonWhen callback says so, or otherwise when $request->expectsJson() is true: a JSON-first Accept header, or an XHR accepting anything. The 422 body is {message, errors}: the first message plus a count, and every field's messages.

open as a page

In Laravel validation, how do you validate every element of a submitted array and keep unexpected nested keys out of the validated data?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Give the parent 'array' (optionally with allowed keys, array:label,url) and write rules per element with the * wildcard, such as links.*.url. When child rules exist, validated() returns only validated nested keys; a bare array rule passes the whole array through.

open as a page

In Laravel, how do you write a custom validation rule class, and when does it run for a missing or empty field?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Run php artisan make:rule to get a class implementing ValidationRule with validate(string $attribute, mixed $value, Closure $fail); call $fail() with a message to reject. Like ordinary rules, it is skipped for absent or empty-string fields unless it sets public $implicit = true.

open as a page

In a Laravel 13 form request, how do you stop validating after the first failing field and redirect failed browser submissions to a named route?

level: middleimportance: nice to knowfreq 28%

basics

~20 s

Set protected $stopOnFirstFailure = true or add #[StopOnFirstFailure]; set $redirectRoute (or $redirect for a URL) or add #[RedirectToRoute('...')] / #[RedirectTo('...')]. Without them, every field is checked and a failed browser submission returns to the previous URL.

open as a page

In Laravel, validating `dependents.*.birth_date` produces "The dependents.1.birth_date field is required." How do you make that message readable for users?

level: seniorimportance: nice to knowfreq 28%

basics

~10 s

Array fields print their raw dot path as :attribute unless named. Map 'dependents..birth_date' in attributes() or validation.attributes, or write a wildcard message such as 'dependents..birth_date.required' using :position to say which row failed.

open as a page