In a Laravel 13 controller, what does $request->validate() return on success, and what happens when validation fails?
answer
- a macro on the Request class
- returns only the validated fields
- throws ValidationException on failure
- redirect back, or 422 for JSON
- 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
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
Know that validate() returns the validated data and that failure sends the user back with errors automatically.
Explain the macro, the thrown ValidationException, the redirect-versus-422 decision and validateWithBag().
Know why the empty base controller broke old $this->validate() calls and when to move to a form request or Validator::make().
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