# How to Create Laravel Custom Validation Rules with Field-Specific Error Messages

> Learn to create Laravel custom validation rules and display field-specific error messages. Master complex validation scenarios in Laravel with ease.

- Repository: [Laravel/framework](https://github.com/laravel/framework)
- Tags: how-to-guide
- Published: 2026-02-16

---

**Laravel's validation engine automatically maps every error to its specific attribute through the `addFailure()` method in [`Validator.php`](https://github.com/laravel/framework/blob/main/Validator.php), enabling field-specific messages via custom rule classes, form request configurations, or attribute aliases.**

When building robust applications with the `laravel/framework` repository, implementing Laravel custom validation rules requires careful attention to error message specificity. The framework's validation architecture ensures that every failure is tied to its exact field, even in complex scenarios involving nested arrays or wildcard patterns.

## Understanding Laravel's Validation Error Architecture

### How Errors Are Mapped to Attributes

In [`src/Illuminate/Validation/Validator.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/Validator.php), the `addFailure()` method (lines 998-1019) serves as the central mechanism for attaching errors to specific fields. When any validation rule fails, the validator calls this method with the raw attribute name (e.g., `users.0.email`), resolves any placeholder hashes, and adds the message to the internal `MessageBag` under that exact key:

```php
$this->messages->add($attribute, $this->makeReplacements(
    $this->getMessage($attributeWithPlaceholders, $rule), $attribute, $rule, $parameters
));

```

Because the key is the fully-qualified attribute, Laravel can later retrieve a field-specific message when you call `$validator->errors()` or `$request->validated()`.

### The Role of Custom Rule Objects

For Laravel custom validation rules implementing `\Illuminate\Contracts\Validation\Rule`, the `validateUsingCustomRule()` method (lines 1515-1550 in [`Validator.php`](https://github.com/laravel/framework/blob/main/Validator.php)) handles message resolution. After the rule's `passes()` method returns `false`, the validator builds the message through a specific hierarchy:

1. It first searches for a message defined for the attribute-rule pair in the validator's local array (`$this->customMessages`).
2. If none exists, it falls back to the rule's own `message()` method.

```php
$messages = $this->getFromLocalArray($originalAttribute, $ruleClass) ?? $rule->message();

```

Thus, the source of specificity is the attribute key that travels through the validator, ensuring that even custom rules produce field-specific errors.

## Implementing Field-Specific Messages in Laravel Custom Validation Rules

### Method 1: Custom Rule Classes with Dynamic Messages

When creating a custom rule class, the `message()` method receives the attribute name, enabling you to compose dynamic, field-specific text:

```php
<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class Uppercase implements Rule
{
    public function passes($attribute, $value)
    {
        return strtoupper($value) === $value;
    }

    // The $attribute argument is the exact field that failed.
    public function message()
    {
        return "The :attribute must be written in uppercase.";
    }
}

```

Usage in a **Form Request**:

```php
public function rules()
{
    return [
        'name'   => ['required', new Uppercase],
        'email'  => ['required', 'email'],
    ];
}

```

If `name` fails, the validator adds an entry under the key `name`, so `$request->errors()->first('name')` returns the custom message.

### Method 2: Per-Field Messages in Form Requests

Override the `messages()` method in your form request to define specific error text for individual fields:

```php
public function messages()
{
    return [
        'email.required' => 'We need to know your email address.',
        'email.email'    => 'The email format looks odd – double‑check it.',
        'users.*.age.min'=> 'Each user must be at least :min years old.',
    ];
}

```

The `users.*.age.min` entry automatically matches any expanded key such as `users.0.age` or `users.1.age`.

### Method 3: Wildcard Validation for Nested Arrays

When validating nested array structures, use wildcard notation in both your rules and messages to maintain field specificity:

```php
public function rules()
{
    return [
        'users.*.email' => 'required|email',
        'users.*.age'   => 'required|integer|min:18',
    ];
}

public function messages()
{
    return [
        'users.*.email.required' => 'User email is mandatory.',
        'users.*.age.min'        => 'Users must be adults (18+).',
    ];
}

```

### Method 4: Friendly Attribute Names

Change the display name of fields using the `attributes()` method to improve message clarity without modifying the message strings:

```php
public function attributes()
{
    return [
        'email' => 'email address',
        'users.*.age' => 'age of each user',
    ];
}

```

Now the `:attribute` placeholder inside any message (including those from custom rules) will be replaced with the friendly string.

## Complex Validation Scenarios

### Handling Nested Array Validation

For deeply nested structures, Laravel's validator expands wildcard patterns before calling `addFailure()`. This means you can define messages for `users.*.profile.bio.required` and Laravel will correctly map failures at `users.0.profile.bio` to your custom text.

### Dynamic Rule Parameters

Custom rule objects receive the attribute name in the `passes()` method, enabling conditional logic based on the field being validated:

```php
public function passes($attribute, $value)
{
    // You can access the specific field name here
    if ($attribute === 'admin_code') {
        return $this->validateAdminFormat($value);
    }
    return $this->validateStandardFormat($value);
}

```

## Key Source Files in laravel/framework

| File | Purpose | Location |
|------|---------|----------|
| [`src/Illuminate/Validation/Validator.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/Validator.php) – `addFailure()` | Adds error messages to the `MessageBag` under the exact attribute key. | [View source](https://github.com/laravel/framework/blob/12.x/src/Illuminate/Validation/Validator.php#L998-L1019) |
| [`src/Illuminate/Validation/Validator.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/Validator.php) – `validateUsingCustomRule()` | Retrieves custom rule messages while preserving the original attribute name. | [View source](https://github.com/laravel/framework/blob/12.x/src/Illuminate/Validation/Validator.php#L1515-L1550) |
| [`src/Illuminate/Validation/Concerns/FormatsMessages.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/Concerns/FormatsMessages.php) | Handles placeholder replacement (`:attribute`, `:value`, etc.) in error messages. | [View source](https://github.com/laravel/framework/blob/12.x/src/Illuminate/Validation/Concerns/FormatsMessages.php) |
| [`src/Illuminate/Validation/Concerns/ValidatesAttributes.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/Concerns/ValidatesAttributes.php) | Provides the `validate*` methods that trigger `addFailure` when checks fail. | [View source](https://github.com/laravel/framework/blob/12.x/src/Illuminate/Validation/Concerns/ValidatesAttributes.php) |

## Summary

- **Laravel stores every validation error under its specific attribute key** via `addFailure()` in [`Validator.php`](https://github.com/laravel/framework/blob/main/Validator.php), ensuring field-specific messaging.
- **Custom rule objects** automatically receive attribute context through `validateUsingCustomRule()`, allowing dynamic message generation.
- **Per-field messages** can be defined in form requests using `field.rule` notation or wildcard patterns like `users.*.email.required`.
- **Friendly attribute names** set via `attributes()` improve message clarity by replacing the `:attribute` placeholder.
- **Nested array validation** maintains specificity through wildcard expansion before error storage.

## Frequently Asked Questions

### How does Laravel associate validation errors with specific fields?

Laravel's `Validator` class uses the `addFailure()` method (lines 998-1019 in [`src/Illuminate/Validation/Validator.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/Validator.php)) to store each error message under the exact attribute key that failed validation. Whether validating simple fields like `email` or nested paths like `users.0.profile.name`, the validator resolves the full attribute path before adding the message to the internal `MessageBag`.

### Can I display different error messages for the same rule on different fields?

Yes. In a form request, override the `messages()` method and use dot notation to target specific field-rule combinations. For example, define `'email.required'` and `'name.required'` with different strings. Alternatively, implement the `message()` method in your custom rule class to return dynamic text based on the `$attribute` parameter passed to the `passes()` method.

### How do I handle validation errors for nested array fields?

Use wildcard notation in both your validation rules and custom messages. Define rules like `users.*.email` and corresponding messages like `users.*.email.required`. Laravel expands these wildcards to concrete indices (e.g., `users.0.email`) before calling `addFailure()`, ensuring that errors appear under the specific nested key. You can also use the `attributes()` method to map `users.*.email` to a friendly name like "user email address."

### What is the difference between the `messages()` and `attributes()` methods in form requests?

The `messages()` method allows you to define the actual error text for specific field-rule combinations, such as returning `['email.required' => 'We need your email']`. The `attributes()` method changes the display name of the field itself by modifying the `:attribute` placeholder replacement, such as mapping `email` to "email address" so that standard messages read "The email address field is required" instead of "The email field is required."