Form Request Validation Tips for Cleaner Controllers
If your controllers are still calling $request->validate([...]) inline, you are leaving a lot of Laravel's best validation features on the table. Form Requests move validation, authorization, and input preparation out of the controller and into a single dedicated class. Here is how to use them well.
Return Only Validated Data
The single most useful habit: never pull raw input into your create and update calls. Use validated() so only the fields that passed validation reach your model:
public function store(StorePostRequest $request)
{
// Only validated keys — no mass-assignment surprises
$post = Post::create($request->validated());
return redirect()->route('posts.show', $post);
}
Need a subset? safe() gives you only() and except():
$data = $request->safe()->only(['title', 'body']);
$data = $request->safe()->except(['published_at']);
Put Authorization Where It Belongs
The authorize() method runs before validation. Use it to keep permission checks out of the controller entirely:
public function authorize(): bool
{
// $this->route('post') resolves the route-model-bound Post
return $this->user()->can('update', $this->route('post'));
}
If it returns false, Laravel throws a 403 automatically. No if statement, no early return in your action.
Prepare Input Before Validation Runs
prepareForValidation() lets you normalize input before the rules are applied. This is the right place to generate slugs, trim strings, or coerce types:
protected function prepareForValidation(): void
{
$this->merge([
'slug' => Str::slug($this->title),
'email' => strtolower($this->email),
]);
}
Now your rules validate the cleaned data, and your controller receives it already normalized.
Clean Up After Validation With passedValidation
When you need to transform data after the rules pass — say, hashing a password or casting a value — passedValidation() runs once validation succeeds:
protected function passedValidation(): void
{
$this->replace([
...$this->validated(),
'password' => Hash::make($this->password),
]);
}
Customize Messages and Attribute Names
Generic error messages frustrate users. Override messages() and attributes() for human-friendly feedback:
public function messages(): array
{
return [
'email.unique' => 'That email is already registered.',
'password.min' => 'Use at least :min characters for your password.',
];
}
public function attributes(): array
{
return ['dob' => 'date of birth'];
}
Add Complex Checks With after
Some validation cannot be expressed as a simple rule — it depends on multiple fields or a database lookup. The after validation hook (available as an array of closures or invokable classes) runs once the basic rules pass:
public function after(): array
{
return [
function (Validator $validator) {
if ($this->ends_at->lt($this->starts_at)) {
$validator->errors()->add(
'ends_at',
'The end date must be after the start date.'
);
}
},
];
}
A well-built Form Request leaves your controller action as just three lines: type-hint the request, do the work, return a response. Everything about what makes the input valid and who is allowed to send it lives in one testable, reusable class. That is the whole point.