Structure of a Custom Validation Rule in laravel-validate: Complete Guide

A custom validation rule in laravel-validate consists of a PHP class stored in src/Rules that implements Illuminate\Contracts\Validation\Rule, which the LaravelValidateServiceProvider automatically registers with Laravel's validator when the using_container option is enabled.

The milwad-dev/laravel-validate package provides over 45 pre-built validation rules for common scenarios like UUIDs, strong passwords, and IBANs. Understanding the internal structure of these rules helps you extend the package or create your own implementations that follow the same architectural patterns.

Anatomy of a Custom Validation Rule

Each rule in the package follows a consistent structural pattern located in the src/Rules directory.

Rule Class Location and Naming Convention

Rule classes reside in src/Rules/ and use PascalCase filenames that map directly to snake_case rule names. For example, ValidUuid.php becomes the valid_uuid validator, while ValidStrongPassword.php exposes valid_strong_password.

The provider scans this directory dynamically:

// In LaravelValidateServiceProvider.php (lines 75-89)
File::files(__DIR__.'/Rules')

This iteration automatically registers every file found, using the basename without the .php extension as the rule identifier.

Interface Implementation

Every rule class implements Laravel's Illuminate\Contracts\Validation\Rule interface, requiring two core methods:

use Illuminate\Contracts\Validation\Rule;

class ValidUuid implements Rule
{
    public function passes($attribute, $value): bool
    {
        return preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $value);
    }

    public function message(): string
    {
        return __('validate.uuid');
    }
}

The passes() method contains the validation logic, while message() returns the error string—typically resolved via translation keys stored in lang/*/validate.php.

Registration Mechanism in LaravelValidateServiceProvider

The LaravelValidateServiceProvider located at src/LaravelValidateServiceProvider.php handles rule registration through its boot() method. Registration only occurs when config('laravel-validate.using_container') evaluates to true.

When enabled, the provider:

  1. Scans src/Rules for all PHP files
  2. Converts filenames to snake_case rule names
  3. Registers each with Laravel's Validator facade

This container-based approach allows you to reference rules as strings rather than instantiating objects manually.

Using Custom Validation Rules

The package supports two primary consumption patterns depending on your configuration.

Object-Based Validation (Direct Instantiation)

Without enabling the container, instantiate rule classes directly in your validation logic:

use Milwad\LaravelValidate\Rules\ValidUuid;

$request->validate([
    'order_id' => ['required', new ValidUuid],
    'username' => ['required', new ValidUsername],
]);

This method works immediately after installation without configuration changes.

String-Based Validation via Container

Enable container registration in config/laravel-validate.php:

return [
    'using_container' => true,
];

Then reference rules as pipe-delimited strings:

$request->validate([
    'order_id' => 'required|valid_uuid',
    'password' => 'required|valid_strong_password',
    'slug' => 'required|valid_slug',
]);

Parameterized Rule Structure

Some rules accept parameters passed via colon separation. The ValidPattern rule demonstrates this structure:

$request->validate([
    'reference_code' => 'required|valid_pattern:/^[A-Z]{3}\d{4}$/',
]);

Country-specific validators like ValidPhoneNumber use similar parameter passing:

// Configuration in config/laravel-validate.php
return [
    'phone-country' => [
        'IR' => ['regex' => '/^09\d{9}$/'],
    ],
];

// Usage
$request->validate([
    'mobile' => 'required|valid_phone_number:IR',
]);

Configuration and Localization Support

The package structure includes dedicated paths for customization:

  • config/laravel-validate.php – Toggles container registration (using_container) and defines country-specific regex patterns for phone and landline validation
  • lang/*/validate.php – Contains translation strings referenced by rule classes (e.g., __('validate.uuid'))

When creating custom rules following this structure, place your language files in the same directory pattern to ensure error messages resolve correctly through Laravel's localization system.

Summary

  • Rule Location: All validation logic resides in src/Rules/ with PascalCase class names
  • Interface Contract: Each rule implements Illuminate\Contracts\Validation\Rule with passes() and message() methods
  • Auto-Registration: LaravelValidateServiceProvider scans the Rules directory and registers classes when using_container is enabled in config
  • Naming Convention: Filenames convert to snake_case rule names (e.g., ValidVatId.php → valid_vatid)
  • Dual Usage: Rules work as instantiated objects immediately or as string shortcuts after enabling container registration
  • Parameter Support: Rules accept parameters via colon-separated strings, parsed within the rule's constructor or passes method

Frequently Asked Questions

How do I create a custom validation rule that follows the laravel-validate structure?

Create a new class in src/Rules/ (or your own package's equivalent directory) that implements Illuminate\Contracts\Validation\Rule. Implement the passes($attribute, $value) method with your validation logic and the message() method to return a translatable string. If extending the package directly, the service provider will auto-register your rule if using_container remains enabled.

Where does laravel-validate store its validation error messages?

Translation strings reside in lang/*/validate.php files within the package directory. Each rule references these via __('validate.rule_name') in its message() method. You can publish these language files to your application to customize the error text.

Can I use laravel-validate rules without enabling the container registration?

Yes. Instantiate rule classes directly using the new keyword, such as new ValidUuid or new ValidCreditCard. This bypasses the LaravelValidateServiceProvider registration logic entirely and works regardless of the using_container configuration setting.

What is the performance impact of the container registration feature?

The service provider scans the src/Rules directory using File::files() only during the boot process when using_container is true. This file system scan occurs once per request in development or during container compilation in production (when config is cached), resulting in minimal runtime overhead.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →