Laravel Magazine

Form Request Validation Tips for Cleaner Controllers

Eric Van Johnson · Tips
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.

Stay Updated

Subscribe to our newsletter

Get latest news, tutorials, community articles and podcast episodes delivered to your inbox.

Weekly articles
We send a new issue of the newsletter every week on Friday.
No spam
We'll never share your email address and you can opt out at any time.