# How to Implement Laravel Custom Validation Rules for Complex Logic

> Master Laravel custom validation rules for complex logic. Learn recommended approaches to create reusable validation objects for cleaner code and efficient application development.

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

---

**Laravel 12 recommends encapsulating complex validation logic in dedicated rule objects that implement `Illuminate\Contracts\Validation\Rule` or extend `Illuminate\Validation\InvokableValidationRule`, enabling dependency injection, reusability, and clean separation from controllers.**

When building applications with the `laravel/framework` repository, you will eventually encounter validation scenarios that exceed the capabilities of built-in rules like `required` or `email`. Implementing **Laravel custom validation rules** through dedicated objects provides a structured, testable approach for handling multi-step validation, external API calls, and cross-field dependencies.

## Why Use Laravel Custom Validation Rules for Complex Logic?

Relying on closure-based validation or inline controller logic quickly becomes unmaintainable. **Custom rule objects** solve this by offering:

- **Separation of Concerns** – Validation logic lives outside controllers and Form Request classes, reducing clutter in your HTTP layer.
- **Reusability** – The same rule can be attached to multiple fields across different requests without code duplication.
- **Testability** – You can unit test the rule class directly without bootstrapping a full HTTP request or hitting the database.
- **Dependency Injection** – Constructors can receive services (e.g., repositories, external APIs) via Laravel’s service container, as implemented in [`src/Illuminate/Validation/Validator.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/Validator.php).

## Three Approaches to Creating Laravel Custom Validation Rules

The framework provides three primary patterns for implementing custom rules, each suited to different complexity levels.

### Standard Rule Objects (Implements Rule Contract)

The most explicit approach requires implementing the **`Illuminate\Contracts\Validation\Rule`** interface, defined in [`src/Illuminate/Contracts/Validation/Rule.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Contracts/Validation/Rule.php). This contract requires two methods: `passes($attribute, $value)` and `message()`.

Use this approach when you need clear method separation and static error messages.

```php
<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;
use App\Services\PhoneNumberService;

class ComplexPhoneNumber implements Rule
{
    protected $service;

    public function __construct(PhoneNumberService $service)
    {
        $this->service = $service;
    }

    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed   $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        // Basic format check
        if (! preg_match('/^\+\d{1,3}\s?\d{7,14}$/', $value)) {
            return false;
        }

        // Call external service for number reputation
        $reputation = $this->service->checkReputation($value);

        // Ensure reputation score is acceptable
        return $reputation->score >= 80;
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return 'The :attribute is not a valid phone number or has a low reputation score.';
    }
}

```

**Usage in a Form Request or Controller:**

```php
use App\Rules\ComplexPhoneNumber;

$request->validate([
    'contact_phone' => ['required', new ComplexPhoneNumber],
]);

```

Because the constructor type-hints `PhoneNumberService`, Laravel’s container automatically injects the service when resolving the rule.

### Invokable Rules (Extends InvokableValidationRule)

For developers who prefer single-method classes, Laravel offers **`Illuminate\Validation\InvokableValidationRule`**, located in [`src/Illuminate/Validation/InvokableValidationRule.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/InvokableValidationRule.php). This base class utilizes PHP’s `__invoke` magic method, making the rule callable as a function.

This approach is ideal when you want cleaner syntax and still need dependency injection, but prefer the simplicity of a single validation method.

```php
<?php

namespace App\Rules;

use Illuminate\Validation\InvokableValidationRule;
use App\Services\ReferralService;

class ValidReferralCode extends InvokableValidationRule
{
    protected $referralService;

    public function __construct(ReferralService $referralService)
    {
        $this->referralService = $referralService;
    }

    /**
     * @param  string $attribute
     * @param  mixed  $value
     * @param  callable $fail
     */
    public function __invoke($attribute, $value, $fail)
    {
        // External API verification
        $result = $this->referralService->verify($value);

        if (! $result->valid) {
            $fail('The referral code is invalid.');
            return;
        }

        // Usage limit check
        if ($result->usageCount >= $result->maxUsage) {
            $fail('The referral code has already reached its usage limit.');
        }
    }
}

```

**Usage:**

```php
use App\Rules\ValidReferralCode;

$request->validate([
    'referral_code' => ['nullable', new ValidReferralCode],
]);

```

### Closure-Based Rules (For Quick Checks)

While [`src/Illuminate/Validation/Validator.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/Validator.php) supports passing anonymous functions to `Validator::make`, this approach is **not recommended** for complex or reusable logic. Closures work for one-off checks but sacrifice testability and separation of concerns.

## Handling Multi-Field Dependencies in Laravel Custom Validation Rules

Complex validation often requires comparing values across multiple fields (e.g., ensuring a **start_date** precedes an **end_date**). You can handle this by passing the entire data set to the rule’s constructor.

```php
<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class ChronologicalDates implements Rule
{
    protected $allData;

    public function __construct(array $data)
    {
        $this->allData = $data;
    }

    public function passes($attribute, $value)
    {
        $start = $this->allData['start_date'] ?? null;
        $end   = $this->allData['end_date'] ?? null;

        if (! $start || ! $end) {
            return false;
        }

        return strtotime($start) < strtotime($end);
    }

    public function message()
    {
        return 'The start date must be earlier than the end date.';
    }
}

```

**Apply in a Form Request:**

```php
public function rules()
{
    return [
        'start_date' => ['required', 'date'],
        'end_date'   => ['required', 'date'],
        'start_date' => [new ChronologicalDates($this->all())],
    ];
}

```

## Key Source Files in the Laravel Framework

Understanding the underlying implementation helps when debugging or extending validation behavior. These files define the contracts and resolution logic for **Laravel custom validation rules**:

- **[`src/Illuminate/Contracts/Validation/Rule.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Contracts/Validation/Rule.php)** – Defines the `passes($attribute, $value)` and `message()` contract that standard rule objects must satisfy.
- **[`src/Illuminate/Validation/InvokableValidationRule.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/InvokableValidationRule.php)** – Base class for invokable rules, providing the `__invoke($attribute, $value, $fail)` signature.
- **[`src/Illuminate/Validation/Validator.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/Validator.php)** – Core validator class that resolves rule objects, handles conditional logic, and executes validation.
- **[`src/Illuminate/Support/Facades/Validator.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Support/Facades/Validator.php)** – Facade providing static access to the validator instance.
- **[`src/Illuminate/Validation/ConditionalRules.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/ConditionalRules.php)** – Handles conditional rule registration such as `sometimes` and `required_if`, useful when custom rules depend on other field states.

## Summary

- **Encapsulate complexity** in dedicated rule classes rather than closures to maximize reusability and testability.
- **Choose the implementation style** based on your needs: implement `Illuminate\Contracts\Validation\Rule` for explicit method separation, or extend `Illuminate\Validation\InvokableValidationRule` for a single-method callable approach.
- **Leverage dependency injection** in rule constructors to access services, APIs, or repositories, as supported by the container resolution in [`src/Illuminate/Validation/Validator.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/Validator.php).
- **Handle cross-field validation** by passing the full data set to your rule’s constructor when comparing multiple inputs.
- **Store rules** in `app/Rules` (or a domain-specific directory) and reference them directly in validation arrays using the `new` keyword.

## Frequently Asked Questions

### What is the difference between Rule objects and Invokable rules in Laravel?

**Rule objects** implement the `Illuminate\Contracts\Validation\Rule` interface, requiring explicit `passes()` and `message()` methods. **Invokable rules** extend `Illuminate\Validation\InvokableValidationRule` and use a single `__invoke()` method that receives a `$fail` callback for error handling. Both support dependency injection, but invokable rules offer a more concise syntax for simple single-check logic.

### Can I inject dependencies into Laravel custom validation rules?

Yes. Because Laravel resolves rule objects through its service container, you can type-hint any class in the constructor. For example, you can inject a `PhoneNumberService` or `ReferralService` directly, and Laravel will automatically instantiate and inject the dependency when the rule is created, as handled in [`src/Illuminate/Validation/Validator.php`](https://github.com/laravel/framework/blob/main/src/Illuminate/Validation/Validator.php).

### How do I validate fields that depend on other fields in Laravel?

Pass the entire request data array to your rule’s constructor. Inside the `passes()` or `__invoke()` method, access the other field values from this stored data set to perform comparisons (e.g., ensuring a start date is before an end date). This approach keeps the validation logic encapsulated while allowing cross-field awareness.

### Where should I store custom validation rules in a Laravel application?

Store custom rules in the `app/Rules` directory, which is the default location when you run `php artisan make:rule RuleName`. For larger applications, organize rules into domain-specific subdirectories (e.g., `app/Rules/Auth`, `app/Rules/Billing`) to maintain a clean architecture and improve discoverability.