Laravel Form Request and Data Validation Tutorial

Valerio Barbera

In this article I will talk about Laravel Form Request to send data from your application frontend to the backend. In web applications, data is usually sent via HTML forms: the data entered by the user into the browser is sent to the server and stored in the database eventually.

Laravel makes it extremely simple to connect the fields of an HTML form with a specific database table, following some conventions related to its implementation of the MVC model (Model/View/Controller).

Laravel Form Request allows you to easily implement the validation step on incoming data, and the authorization logic for the request. In addition to the validate method that can be used to specify all available validation rules (presence of a field, length, regular expressions, etc.)

If you want to learn how to create your custom validation rules in Laravel, you can read this tutorial:

For more technical articles you can follow me on Linkedin or X.

Laravel Form Request Lifecycle

The HTTP Request comes into the system through the Router to find the controller associated with the requested endpoint. Before passing the request into the controller the router runs the middleware chain for that specific endpoint, and then the request is injected into the controller’s method.

class BookController extends Controller 
{
    public function store(Request $request)
    {
        return Book::create($request->all());
    }
}

Typically authorization and data validation are done in the controller. This is typically the case because the Laravel base controller every custom controller class extends provides a perfect integration with the Laravel authorization system. 

And the data validation step can be done calling the validate method on the request instance:

class BookController extends Controller 
{
    public function store(Request $request)
    {
        // Authorize the user action
        $this->authorize('create', Book::class);
	
        // Validate incoming data
        $request->validate([
            'title' => ['required', 'max:200'],
            'author_id' => ['required', 'exists:users,id'],
        ]);
		
        // Return the response
        return Book::create($request->all());
    }
}

For simple tasks similar to the example above it’s totally fine to implement them into the controller. But sometimes validation, authorization, or both, can be tricky and very different from one method to another (store, update, or other actions).

You could need to customize error messages or implement intermediate methods that could make the Controller a mess.

In this case you can make your life easier by implementing a dedicated object. A Form Request precisely.

What is a Laravel Form Request

basically a Laravel Form Request is an extension of the basic http request class Illuminate/Http/Request that encapsulates their own validation and authorization logic.

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreBookRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            //
        ];
    }
}

You can create a Form Request with the following command:

php artisan make:request StoreBookRequest

Authorization

In the authorize method you can get the logged in user to verify its ability to perform this action. The standard user model in Laravel already has the can() method available to verify permission based on your Policy classes:

class StoreBookRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return $this->user()->can('create', Book::class);
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            //
        ];
    }
}

Or leverage the Route-Model binding to get access also to the target object:

// Update book route
Route::put('books/{book}');

// Access the given Book object
public function authorize(): bool
{
    return $this->user()->can('update', $this->book);
}

Validation

More features are available for data validation in the Form Request class. You can basically start encapsulating the standard validation logic:

class StoreBookRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'title' => ['required', 'max:200'],
            'author_id' => ['required', 'exists:users,id'],
        ];
    }
}

Now that you are in a Form Request class you can perform additional validation steps using hooks, like after:

use App\Validation\ValidateUserStatus;

class StoreBookRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'title' => ['required', 'max:200'],
            'author_id' => ['required', 'exists:users,id'],
        ];
    }
	
    /**
     * Get the "after" validation callables for the request.
     */
    public function after(): array
    {
        return [
            new ValidateUserStatus,
	
            function (Validator $validator) {
                // Custom validator
            },
        ];
    }
}

Customizing Laravel validation messages

If you need to customize validation error messages you could do it in the traditional way inside the controller passing the custom messages as second argument of the request’s validate method:

public function store(Request $request)
{
    $request->validate(
        // Rules
        ['title' => ['required', 'max:200']],

        // Custom validation messages
        ['title.required' => 'Title field is required']
    );
}

Clearly if the number of fields under validation increase, adding also custom messages can put more pressure on the code organization inside the Controller. Form request can encapsulate custom message too with ease:

use App\Validation\ValidateUserStatus;

class StoreBookRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'title' => ['required', 'max:200'],
            'author_id' => ['required', 'exists:users,id'],
        ];
    }
	
    /**
     * Get the "after" validation callables for the request.
     */
    public function after(): array
    {
        return [
            new ValidateUserStatus,
			
            function (Validator $validator) {
                // Custom validator
            },
        ];
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function messages(): array
    {
        return [
            'title.required' => ['Title field is required.'],
            'author_id.required' => ['You need to associate an author to the book.'],
        ];
    }
}

As shown by the snippets above using From Request you can decouple the controller from authorization and validation logic for specific actions, making your life much easier reading and navigating your application code.

For more technical articles you can follow me on Linkedin or X.

Monitor your Laravel application for free

Inspector is a Code Execution Monitoring tool specifically designed for software developers. You don’t need to install anything on the infrastructure, just install the Laravel package and you are ready to go.

Inspector is super easy to use and require zero configurations.

If you are looking for HTTP monitoring, query insights, and the ability to forward alerts and notifications into your preferred messaging environment try Inspector for free. Register your account.

Or learn more on the website: https://inspector.dev

Inspector Code Execution Monitoring

Related Posts

How to Stop a Streamed AI Response Mid-Flight in Neuron AI v3

One thing I didn’t anticipate when building Neuron AI was how many edge cases would surface not from the AI integration itself, but from the UI layer sitting on top of it. Developers don’t just want agents that work. They want agents that feel right to use. And the moment you start building chat interfaces

Conversational Data Collection: Introducing AIForm

One of the more interesting things about building an open-source framework is that the community often knows what to build next before you do. When I started Neuron AI, I had a fairly clear picture in my head of the core primitives: agents, tools, workflows, structured output. What I didn’t fully anticipate was how quickly

Neuron AI Now Supports ZAI — The GLM Series Is Worth Your Attention

There’s a pattern I’ve noticed over the past year while working on Neuron AI: the decisions that matter most are rarely about chasing trends. They’re about quietly recognizing something that works, testing it seriously, and integrating it so that other developers can benefit without having to do that work themselves. That’s the honest story behind