Form Validation in Laravel with Real Examples

Form Validation in Laravel with Real Examples

When you build a web application, forms are everywhere. Login forms, registration forms, contact forms, checkout forms, profile forms, admin forms, search forms, comment forms — almost every useful application depends on user input. And because users are human, that input is rarely perfect the first time. Someone forgets to type their email. Someone uploads the wrong file type. Someone writes a name that is too short. Someone leaves a required field empty. Someone enters a password that is too weak.

That is exactly why validation matters so much.

In Laravel, form validation is one of the nicest parts of the framework. It is expressive, readable, and powerful enough for small projects and large production systems. You can start with a few simple rules and gradually move into advanced scenarios like nested array validation, conditional rules, custom messages, and reusable Form Request classes. The beauty of Laravel validation is that it lets you keep your application clean while protecting your data from invalid or harmful input.

In this article, we will go deep into form validation in Laravel with real examples. We will not just talk about theory. We will build practical examples step by step so you can understand how validation works in real projects. By the end, you will know how to validate common forms, customize error messages, handle file uploads, validate arrays, use Form Request classes, and write validation logic that feels professional and maintainable.

Why Validation Matters So Much

Validation is not just about showing red error messages under a form field. It is about protecting your application, your database, and your users.

Imagine a registration form that accepts any kind of value. A user might enter text into a phone number field, or submit an empty email, or upload a huge unsupported file. Without validation, your application could save broken data, crash in unexpected ways, or create a bad user experience that feels sloppy and untrustworthy.

Good validation helps in several ways. It keeps your database clean. It improves user experience by showing clear guidance. It reduces bugs caused by bad input. It helps protect your application from common mistakes and input-related issues. And in a well-designed app, validation also acts like a conversation with the user. Instead of silently failing, the form tells them exactly what needs to be corrected.

Laravel understands this very well, which is why its validation system is designed to be developer-friendly and easy to read.

A Simple Example: Validating a Contact Form

Let us begin with one of the most common examples: a contact form.

Suppose you have a form with fields for name, email, subject, and message. You want to make sure that the name and email are required, the email is valid, the subject is not too short, and the message is long enough to be meaningful.

Blade Form Example

<form action="{{ route('contact.submit') }}" method="POST">
    @csrf

    <div>
        <label for="name">Name</label>
        <input type="text" name="name" id="name" value="{{ old('name') }}">
        @error('name')
            <p>{{ $message }}</p>
        @enderror
    </div>

    <div>
        <label for="email">Email</label>
        <input type="email" name="email" id="email" value="{{ old('email') }}">
        @error('email')
            <p>{{ $message }}</p>
        @enderror
    </div>

    <div>
        <label for="subject">Subject</label>
        <input type="text" name="subject" id="subject" value="{{ old('subject') }}">
        @error('subject')
            <p>{{ $message }}</p>
        @enderror
    </div>

    <div>
        <label for="message">Message</label>
        <textarea name="message" id="message">{{ old('message') }}</textarea>
        @error('message')
            <p>{{ $message }}</p>
        @enderror
    </div>

    <button type="submit">Send Message</button>
</form>

This Blade form uses old() to preserve input if validation fails, which makes the experience much better for the user. Nobody likes retyping everything because of one missing field. The @error directive displays the validation message for each field.

Controller Example

use Illuminate\Http\Request;

public function submit(Request $request)
{
    $validated = $request->validate([
        'name'    => 'required|string|max:100',
        'email'   => 'required|email|max:255',
        'subject' => 'required|string|min:5|max:150',
        'message' => 'required|string|min:20',
    ]);

    // Save the data, send an email, or perform another action
    // ...

    return back()->with('success', 'Your message has been sent successfully.');
}

This is one of the simplest ways to validate data in Laravel. If validation fails, Laravel automatically redirects back to the form and sends the errors to the session. If validation passes, the $validated array contains only valid data.

That is already useful, but this is only the beginning.

Understanding Laravel Validation Rules

Laravel validation rules are the heart of the system. You can combine multiple rules on one field using a pipe | or an array.

Here are some of the most common rules you will use often:

  • required ensures the field must not be empty

  • string ensures the value is a string

  • email ensures the value is a valid email address

  • min and max control length for strings or size for files

  • confirmed checks that a field has a matching confirmation field

  • unique checks that the value does not already exist in the database

  • exists checks that the value exists in the database

  • numeric ensures the value is a number

  • date ensures the value is a valid date

  • boolean ensures the value is true or false

  • image validates image uploads

  • mimes and mimetypes validate file types

A rule set may look simple, but these rules give you a lot of control.

A Real Registration Form Example

Registration forms are perfect for learning validation because they usually involve several important fields and at least a few business rules.

Suppose we want to register a user with these fields:

  • name

  • email

  • password

  • password confirmation

  • terms acceptance

Controller Example

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Models\User;

public function register(Request $request)
{
    $validated = $request->validate([
        'name' => 'required|string|min:3|max:100',
        'email' => 'required|email|unique:users,email|max:255',
        'password' => 'required|string|min:8|confirmed',
        'terms' => 'accepted',
    ]);

    User::create([
        'name' => $validated['name'],
        'email' => $validated['email'],
        'password' => Hash::make($validated['password']),
    ]);

    return redirect()->route('login')->with('success', 'Account created successfully.');
}

This example shows a few very important ideas.

First, the unique:users,email rule prevents duplicate email addresses in the users table. That is essential in most applications. Second, the confirmed rule requires a matching password_confirmation field. Third, the password is hashed before saving. Validation and security go hand in hand. A form may validate successfully, but that does not mean every value should be saved directly as-is.

Blade Form Example

<form action="{{ route('register.submit') }}" method="POST">
    @csrf

    <div>
        <label>Name</label>
        <input type="text" name="name" value="{{ old('name') }}">
        @error('name')
            <p>{{ $message }}</p>
        @enderror
    </div>

    <div>
        <label>Email</label>
        <input type="email" name="email" value="{{ old('email') }}">
        @error('email')
            <p>{{ $message }}</p>
        @enderror
    </div>

    <div>
        <label>Password</label>
        <input type="password" name="password">
        @error('password')
            <p>{{ $message }}</p>
        @enderror
    </div>

    <div>
        <label>Confirm Password</label>
        <input type="password" name="password_confirmation">
    </div>

    <div>
        <label>
            <input type="checkbox" name="terms" value="1">
            I agree to the terms
        </label>
        @error('terms')
            <p>{{ $message }}</p>
        @enderror
    </div>

    <button type="submit">Register</button>
</form>

This is a common pattern, and it already covers a large percentage of real-world form validation cases.

Using Custom Error Messages

Laravel gives you default validation messages, but in real projects, custom messages often feel much better. They sound more natural, match your brand tone, and can be written in the language of your audience.

Here is an example of custom messages inside the controller.

public function register(Request $request)
{
    $validated = $request->validate(
        [
            'name' => 'required|string|min:3|max:100',
            'email' => 'required|email|unique:users,email|max:255',
            'password' => 'required|string|min:8|confirmed',
            'terms' => 'accepted',
        ],
        [
            'name.required' => 'Please enter your full name.',
            'name.min' => 'Your name must be at least 3 characters long.',
            'email.required' => 'Email address is required.',
            'email.email' => 'Please enter a valid email address.',
            'email.unique' => 'This email is already registered.',
            'password.required' => 'Please create a password.',
            'password.min' => 'Your password must be at least 8 characters.',
            'password.confirmed' => 'The password confirmation does not match.',
            'terms.accepted' => 'You must accept the terms before continuing.',
        ]
    );

    // ...
}

This approach is helpful, but once your form grows larger, putting everything inside the controller can make the code feel crowded. That is where Form Requests become incredibly useful.

Form Request Validation: Cleaner and More Professional

Form Request classes are one of the nicest features in Laravel validation. They allow you to move validation logic out of your controller and into a dedicated class. This keeps controllers cleaner and makes your validation easier to manage.

To create a Form Request, you can use:

php artisan make:request RegisterUserRequest

Form Request Example

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class RegisterUserRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'name' => 'required|string|min:3|max:100',
            'email' => 'required|email|unique:users,email|max:255',
            'password' => 'required|string|min:8|confirmed',
            'terms' => 'accepted',
        ];
    }

    public function messages(): array
    {
        return [
            'name.required' => 'Please enter your full name.',
            'email.unique' => 'This email is already registered.',
            'password.confirmed' => 'The password confirmation does not match.',
            'terms.accepted' => 'You must accept the terms before continuing.',
        ];
    }
}

Controller Using Form Request

use App\Http\Requests\RegisterUserRequest;
use App\Models\User;
use Illuminate\Support\Facades\Hash;

public function register(RegisterUserRequest $request)
{
    $validated = $request->validated();

    User::create([
        'name' => $validated['name'],
        'email' => $validated['email'],
        'password' => Hash::make($validated['password']),
    ]);

    return redirect()->route('login')->with('success', 'Account created successfully.');
}

This version looks much cleaner. The controller focuses on what happens after validation, while the Form Request focuses on the rules themselves. In larger applications, this separation is extremely valuable.

Real Example: Validating a Blog Post Form

Let us move to a more realistic content management example. Imagine you are building a blog admin panel. You want to create a form for posts with the following fields:

  • title

  • slug

  • excerpt

  • body

  • category_id

  • published

  • cover_image

The validation should make sure the title and slug are unique, the body is long enough, the category exists, and the uploaded image has the right format.

Form Request Example

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StorePostRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'title' => 'required|string|min:5|max:200',
            'slug' => 'required|string|min:5|max:200|unique:posts,slug',
            'excerpt' => 'nullable|string|max:500',
            'body' => 'required|string|min:100',
            'category_id' => 'required|exists:categories,id',
            'published' => 'nullable|boolean',
            'cover_image' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:2048',
        ];
    }

    public function messages(): array
    {
        return [
            'title.required' => 'Post title is required.',
            'slug.unique' => 'This slug is already in use.',
            'body.min' => 'The post body must contain at least 100 characters.',
            'category_id.exists' => 'Please select a valid category.',
            'cover_image.image' => 'The cover image must be a valid image file.',
            'cover_image.mimes' => 'Only JPG, JPEG, PNG, and WEBP files are allowed.',
            'cover_image.max' => 'The cover image must not exceed 2MB.',
        ];
    }
}

Controller Example

use App\Http\Requests\StorePostRequest;
use App\Models\Post;
use Illuminate\Support\Str;

public function store(StorePostRequest $request)
{
    $validated = $request->validated();

    $data = [
        'title' => $validated['title'],
        'slug' => $validated['slug'],
        'excerpt' => $validated['excerpt'] ?? null,
        'body' => $validated['body'],
        'category_id' => $validated['category_id'],
        'published' => $request->boolean('published'),
    ];

    if ($request->hasFile('cover_image')) {
        $data['cover_image'] = $request->file('cover_image')->store('posts', 'public');
    }

    Post::create($data);

    return redirect()->route('posts.index')->with('success', 'Post created successfully.');
}

This is a very practical example because it combines text validation, relational validation, boolean handling, and file upload validation in one form.

Validating File Uploads Properly

File validation is one area where beginners often make mistakes. A file input may look simple, but you need to think about format, size, and sometimes dimensions.

Suppose you are allowing profile photo uploads. You probably want to accept only images, with a limited file size, and maybe with a specific width and height.

Example

public function updateProfile(Request $request)
{
    $validated = $request->validate([
        'name' => 'required|string|min:3|max:100',
        'avatar' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:2048',
    ]);

    if ($request->hasFile('avatar')) {
        $path = $request->file('avatar')->store('avatars', 'public');
    }

    // Save profile data
}

If you want to validate image dimensions, Laravel provides a dimensions rule.

public function updateProfile(Request $request)
{
    $validated = $request->validate([
        'avatar' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:2048|dimensions:min_width=300,min_height=300',
    ]);
}

This is useful when your UI requires a square or large enough image. It prevents users from uploading tiny images that look blurry or break your design.

Validating Arrays and Nested Inputs

Modern forms often include arrays. Think of product variations, shopping cart options, multiple images, tags, team members, or repeated form groups. Laravel handles array validation very well.

Imagine a product form with multiple tags:

<input type="text" name="tags[]" />
<input type="text" name="tags[]" />
<input type="text" name="tags[]" />

You can validate them like this:

public function store(Request $request)
{
    $validated = $request->validate([
        'title' => 'required|string|min:3|max:255',
        'tags' => 'nullable|array',
        'tags.*' => 'nullable|string|min:2|max:50',
    ]);
}

The tags.* syntax means “validate each item in the tags array.” This is very powerful and very common in real forms.

Example with Nested Data

Suppose you are collecting multiple order items:

<input type="text" name="items[0][name]" />
<input type="number" name="items[0][quantity]" />
<input type="text" name="items[1][name]" />
<input type="number" name="items[1][quantity]" />

Validation:

public function storeOrder(Request $request)
{
    $validated = $request->validate([
        'customer_name' => 'required|string|max:100',
        'items' => 'required|array|min:1',
        'items.*.name' => 'required|string|min:2|max:255',
        'items.*.quantity' => 'required|integer|min:1',
    ]);
}

This kind of validation is very useful in admin panels, inventory systems, quotation forms, and checkout systems.

Validating a Login Form

Login validation often looks simple, but it is still important. A login form typically needs an email and password.

Example

public function login(Request $request)
{
    $credentials = $request->validate([
        'email' => 'required|email',
        'password' => 'required|string',
    ]);

    if (auth()->attempt($credentials)) {
        $request->session()->regenerate();
        return redirect()->intended('/dashboard');
    }

    return back()->withErrors([
        'email' => 'The provided credentials are incorrect.',
    ])->onlyInput('email');
}

Notice that we do not give away too much information. A generic error message is better for security than saying exactly which field is wrong in a way that helps attackers.

Conditional Validation Rules

Some fields should only be required if another field has a certain value. This is very common in real projects.

For example, imagine a form where users can choose whether they want delivery or pickup. If they select delivery, then an address must be provided.

Example

public function checkout(Request $request)
{
    $validated = $request->validate([
        'order_type' => 'required|in:delivery,pickup',
        'address' => 'required_if:order_type,delivery|string|max:255',
        'phone' => 'required|string|max:20',
    ]);
}

Here, address is only required when order_type is delivery.

Another useful rule is nullable, which means the field can be empty, but if it has a value, that value must still pass validation.

You can also use sometimes for more dynamic validation logic.

$request->validate([
    'discount_code' => 'sometimes|string|min:3|max:20',
]);

Validating Unique Data During Updates

One of the most common mistakes in Laravel validation happens when updating records. Suppose you want to edit a user profile. The email should remain unique, but the current user’s email should not trigger a duplicate error if they did not change it.

This is where the ignore() method becomes useful.

Example

use Illuminate\Validation\Rule;

public function update(Request $request, User $user)
{
    $validated = $request->validate([
        'name' => 'required|string|min:3|max:100',
        'email' => [
            'required',
            'email',
            Rule::unique('users', 'email')->ignore($user->id),
        ],
    ]);

    $user->update($validated);

    return back()->with('success', 'Profile updated successfully.');
}

This is much safer and cleaner than trying to write custom logic yourself. It tells Laravel to check uniqueness while ignoring the current record.

Customizing Attribute Names for Friendlier Messages

Sometimes validation messages sound too technical. Instead of saying The first_name field is required, you may want to display First name is required.

Laravel lets you customize attribute names.

Example in Form Request

public function attributes(): array
{
    return [
        'first_name' => 'first name',
        'last_name' => 'last name',
        'phone_number' => 'phone number',
    ];
}

Now your validation messages become more natural and user-friendly.

This small detail makes a big difference. A form that feels human and clear is often much more pleasant to use than one that feels robotic.

Real Example: Validating a Product Form for an E-Commerce Site

E-commerce forms are excellent validation examples because they contain many field types. Let us say you are adding a product with:

  • name

  • sku

  • price

  • stock

  • description

  • category_id

  • images

  • status

Form Request Example

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class StoreProductRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'name' => 'required|string|min:3|max:255',
            'sku' => ['required', 'string', 'max:100', Rule::unique('products', 'sku')],
            'price' => 'required|numeric|min:0',
            'stock' => 'required|integer|min:0',
            'description' => 'nullable|string|max:2000',
            'category_id' => 'required|exists:categories,id',
            'status' => 'required|in:active,inactive,draft',
            'images' => 'nullable|array',
            'images.*' => 'image|mimes:jpg,jpeg,png,webp|max:2048',
        ];
    }

    public function messages(): array
    {
        return [
            'name.required' => 'Product name is required.',
            'sku.unique' => 'This SKU already exists.',
            'price.numeric' => 'The price must be a valid number.',
            'stock.integer' => 'Stock must be an integer value.',
            'category_id.exists' => 'Please choose a valid category.',
            'status.in' => 'Please choose a valid product status.',
            'images.*.image' => 'Each uploaded file must be an image.',
        ];
    }
}

This example is close to what you might use in a real business application.

Controller Example

use App\Http\Requests\StoreProductRequest;
use App\Models\Product;

public function store(StoreProductRequest $request)
{
    $validated = $request->validated();

    $product = Product::create([
        'name' => $validated['name'],
        'sku' => $validated['sku'],
        'price' => $validated['price'],
        'stock' => $validated['stock'],
        'description' => $validated['description'] ?? null,
        'category_id' => $validated['category_id'],
        'status' => $validated['status'],
    ]);

    if ($request->hasFile('images')) {
        foreach ($request->file('images') as $image) {
            $path = $image->store('products', 'public');
            // Save image relation if needed
        }
    }

    return redirect()->route('products.index')->with('success', 'Product created successfully.');
}

Validation is not only about preventing bad data. It also helps define what “good data” means in your application.

Validating Dates and Time

Date validation is another area where you need precision. Consider an event booking form.

Fields might include:

  • event_date

  • start_time

  • end_time

  • booking_type

Example

public function storeEvent(Request $request)
{
    $validated = $request->validate([
        'event_name' => 'required|string|min:3|max:255',
        'event_date' => 'required|date|after:today',
        'start_time' => 'required',
        'end_time' => 'required',
    ]);
}

You can also compare dates using Laravel’s date rules.

$request->validate([
    'start_date' => 'required|date',
    'end_date' => 'required|date|after_or_equal:start_date',
]);

This is especially useful in booking systems, hotel reservations, appointments, and scheduling apps.

Real Example: Validating a Profile Update Form

Profile forms are common, but they often include optional values. The user might want to change their name, phone number, bio, or avatar.

Example

public function updateProfile(Request $request)
{
    $validated = $request->validate([
        'name' => 'required|string|min:3|max:100',
        'phone' => 'nullable|string|min:8|max:20',
        'bio' => 'nullable|string|max:500',
        'avatar' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:2048',
    ]);

    $user = auth()->user();
    $user->name = $validated['name'];
    $user->phone = $validated['phone'] ?? null;
    $user->bio = $validated['bio'] ?? null;

    if ($request->hasFile('avatar')) {
        $user->avatar = $request->file('avatar')->store('avatars', 'public');
    }

    $user->save();

    return back()->with('success', 'Profile updated successfully.');
}

This type of form is extremely common, and validation keeps it safe and consistent.

Handling Validation in AJAX Requests

In modern applications, forms are often submitted through AJAX instead of a full page reload. Laravel still handles validation nicely here.

When validation fails in an AJAX request, Laravel usually returns a JSON response with the validation errors.

Example

public function storeComment(Request $request)
{
    $validated = $request->validate([
        'post_id' => 'required|exists:posts,id',
        'content' => 'required|string|min:5|max:1000',
    ]);

    Comment::create($validated);

    return response()->json([
        'message' => 'Comment added successfully.',
    ]);
}

If the request expects JSON, Laravel will return validation errors in JSON format automatically. This is very convenient for frontend applications built with Vue, React, Alpine, or even plain JavaScript.

Using validateWithBag for Separate Error Bags

Sometimes a page has more than one form on it. For example, a profile page might have one form for updating personal info and another for changing password. In that case, error messages can become confusing if they all appear together.

Laravel allows you to use separate error bags.

Example

public function updatePassword(Request $request)
{
    $request->validateWithBag('passwordUpdate', [
        'current_password' => 'required',
        'password' => 'required|string|min:8|confirmed',
    ]);
}

Then, in Blade, you can reference that error bag specifically.

@error('password', 'passwordUpdate')
    <p>{{ $message }}</p>
@enderror

This keeps forms organized and prevents validation messages from leaking into the wrong place.

Advanced Validation with Rule Objects

Sometimes string-based rules are not enough. Laravel also allows rule objects, which can make complex validation more readable.

For example, the unique rule with ignore logic is often better written using Rule::unique().

use Illuminate\Validation\Rule;

$request->validate([
    'email' => [
        'required',
        'email',
        Rule::unique('users')->ignore($user->id),
    ],
]);

Rule objects are especially useful when validation conditions start becoming more dynamic or when you want to write code that is easier to understand later.

Real Example: Validating a Payment or Checkout Form

Checkout forms are sensitive because they often contain a mix of address, shipping, contact, and payment-related fields.

A simplified example might look like this:

public function checkout(Request $request)
{
    $validated = $request->validate([
        'full_name' => 'required|string|min:3|max:100',
        'email' => 'required|email|max:255',
        'phone' => 'required|string|max:20',
        'address' => 'required|string|max:255',
        'city' => 'required|string|max:100',
        'postal_code' => 'required|string|max:20',
        'country' => 'required|string|max:100',
        'payment_method' => 'required|in:card,cash_on_delivery,paypal',
    ]);
}

If the order depends on country-specific rules or shipping methods, you can extend validation further with conditional logic.

The important thing is to think about the business process, not just the form field. Validation should match the real-world rules of your application.

Using sometimes for Flexible Validation

There are cases where a field should only be validated under certain conditions. Laravel’s sometimes method is helpful in that case.

Example

$validator = Validator::make($request->all(), [
    'coupon_code' => 'nullable|string|max:50',
]);

$validator->sometimes('coupon_code', 'required|string|min:5', function ($input) {
    return $input->has_discount === true;
});

$validated = $validator->validate();

This allows the rules to change based on the submitted data. Flexible validation like this is useful in conditional workflows.

Validating Strong Passwords

Passwords deserve extra attention. A good password rule should enforce length and potentially complexity.

Example

$request->validate([
    'password' => 'required|string|min:8|confirmed',
]);

For more advanced password validation, Laravel also supports password rule helpers depending on your version and configuration. In modern apps, you may want stronger policies that require letters, numbers, symbols, or mixed-case characters.

A password is not just a form field. It is part of your application’s security boundary, so validation here matters a great deal.

Making Error Messages Feel Human

This part is often underestimated. Validation messages should not only be correct; they should sound like they were written by a thoughtful person.

Compare these two styles:

“validation.required”
versus
“Please enter your name so we can continue.”

The second one feels warmer and more useful. It does not sound like a machine yelling at the user. It sounds like a guide.

For example:

public function messages(): array
{
    return [
        'name.required' => 'Please tell us your name.',
        'email.required' => 'We need your email address so we can contact you.',
        'password.min' => 'Your password should be at least 8 characters long.',
    ];
}

That small touch can make a form feel far more polished.

A Full Example: Article Submission Form

Let us put everything together in a realistic example. Suppose a content platform has an article submission form.

Fields:

  • title

  • slug

  • category_id

  • summary

  • content

  • cover_image

  • tags

  • published_at

Form Request

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class StoreArticleRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'title' => 'required|string|min:10|max:255',
            'slug' => ['required', 'string', 'min:10', 'max:255', Rule::unique('articles', 'slug')],
            'category_id' => 'required|exists:categories,id',
            'summary' => 'required|string|min:50|max:500',
            'content' => 'required|string|min:500',
            'cover_image' => 'nullable|image|mimes:jpg,jpeg,png,webp|max:3072',
            'tags' => 'nullable|array',
            'tags.*' => 'string|min:2|max:50',
            'published_at' => 'nullable|date|after_or_equal:today',
        ];
    }

    public function messages(): array
    {
        return [
            'title.min' => 'Your article title should be descriptive.',
            'slug.unique' => 'This slug is already taken.',
            'summary.min' => 'The summary should give readers a clear idea of the article.',
            'content.min' => 'The article content should be detailed and helpful.',
            'published_at.after_or_equal' => 'The publish date cannot be in the past.',
        ];
    }

    public function attributes(): array
    {
        return [
            'category_id' => 'category',
            'cover_image' => 'cover image',
            'published_at' => 'publish date',
        ];
    }
}

Controller

use App\Http\Requests\StoreArticleRequest;
use App\Models\Article;

public function store(StoreArticleRequest $request)
{
    $validated = $request->validated();

    $article = Article::create([
        'title' => $validated['title'],
        'slug' => $validated['slug'],
        'category_id' => $validated['category_id'],
        'summary' => $validated['summary'],
        'content' => $validated['content'],
        'published_at' => $validated['published_at'] ?? null,
    ]);

    if ($request->hasFile('cover_image')) {
        $article->cover_image = $request->file('cover_image')->store('articles', 'public');
        $article->save();
    }

    return redirect()->route('articles.index')->with('success', 'Article submitted successfully.');
}

This is the kind of validation you would be proud to keep in a real codebase.

Common Mistakes to Avoid

Even though Laravel makes validation easy, there are still a few mistakes people often make.

One common mistake is forgetting to validate on the server side and relying only on frontend validation. Frontend validation is useful, but it is never enough by itself because users can bypass it.

Another mistake is overusing controller validation in very large applications. It works, but eventually your controllers become crowded and difficult to maintain. Form Requests usually solve that problem.

A third mistake is not validating files properly. Accepting raw uploads without restrictions can lead to security and storage issues.

A fourth mistake is writing error messages that are too technical or unfriendly. Good validation should help people, not confuse them.

A fifth mistake is forgetting to handle update scenarios differently from create scenarios, especially with unique rules.

These may seem like small details, but together they make a big difference in the quality of your application.

Best Practices for Laravel Form Validation

A good validation strategy is simple, consistent, and easy to maintain.

Use Form Request classes for medium and large applications. Keep your controllers focused on business logic, not validation clutter. Write custom messages when user experience matters. Validate file uploads carefully. Use old() in Blade to preserve user input after validation errors. Customize attribute names when default field names feel too technical. Validate arrays with * rules when using repeated fields. Handle update forms properly with ignore(). And always remember that validation is both a usability feature and a security feature.

A clean validation layer often reflects a mature application overall. It shows that the developer has thought about how users actually behave, not just how the code should look.

A Friendly Final Thought

Laravel validation is one of those features that feels simple at first, but becomes more valuable the more you use it. At the beginning, it may just be about checking required fields. Later, it becomes your way of enforcing business rules, protecting your data, and guiding users gently through your forms.

That is what makes it so useful in real projects. Good validation is not just a technical detail. It is part of the experience. It is the quiet layer that helps your application feel reliable, polished, and respectful of the person using it.

When your validation is done well, users do not always notice it directly. They simply feel that the form makes sense, that mistakes are easy to fix, and that the application understands them. That is a sign of good software.

#Laravel form validation #Laravel validation examples #Laravel validation tutorial #Form Request validation #custom validation messages Laravel #Laravel file validation #Laravel array validation #Laravel real examples #Laravel beginner guide #secure Laravel forms

Subscribe to our newsletter

12k+

Subscribers

Weekly

Frequency

Free

Always