Muhammad Yasir Amin

Loading websiteโ€ฆ

More from Muhammad Yasir Amin

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Subscribe on YouTube

How to Create Custom Validation Rules in Laravel 13

๐Ÿ› ๏ธ Laravel Tutorials4 min read

How to Create Custom Validation Rules in Laravel 13

Muhammad Yasir Amin

Muhammad Yasir Amin

Senior Full Stack Engineerโ€ขNov 8, 2026
How to Create Custom Validation Rules in Laravel 13

To create a custom validation rule in Laravel 13, run php artisan make:rule, implement the validate() method of the ValidationRule interface, call $fail() when the value is invalid, and add the rule object to your rules array. For one-off checks, a closure with the same signature works without a new class.

What do you need before you start?

  • Laravel 13 on PHP 8.3 or newer
  • A form request or controller that already validates input

How do you generate a custom rule class?

Code
php artisan make:rule StrongUsername

The command creates app/Rules/StrongUsername.php. The class implements Illuminate\Contracts\Validation\ValidationRule and has one method, validate(), which receives the attribute name, the value and a $fail closure.

How do you write the rule logic?

This example rejects usernames that are too short, start with a digit, or contain characters outside letters, numbers and underscores.

JavaScript
<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class StrongUsername implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (! is_string($value) || strlen($value) < 4) {
            $fail('The :attribute must be at least 4 characters.');

            return;
        }

        if (ctype_digit($value[0])) {
            $fail('The :attribute must not start with a number.');
        }

        if (! preg_match('/^[A-Za-z0-9_]+$/', $value)) {
            $fail('The :attribute may only contain letters, numbers and underscores.');
        }
    }
}

The :attribute placeholder is replaced with the field name. Each $fail() call adds one error message.

How do you use the rule in a request?

JavaScript
use App\Rules\StrongUsername;

$request->validate([
    'username' => ['required', 'string', new StrongUsername],
]);

In a form request, return the same array from rules(). Put cheap built-in rules such as required first so the custom class only runs on plausible input.

Can you use a closure instead of a class?

Yes. A closure rule has the same signature and suits logic you will not reuse.

JavaScript
use Closure;

$request->validate([
    'coupon' => [
        'required',
        function (string $attribute, mixed $value, Closure $fail) {
            if (! in_array(strtoupper($value), ['SPRING10', 'WELCOME5'], true)) {
                $fail("The {$attribute} is not valid.");
            }
        },
    ],
]);

How do you give a rule access to other fields?

Implement DataAwareRule to receive all validated data, or ValidatorAwareRule to receive the validator instance. The rule class gets a setData() or setValidator() method that Laravel calls before validation.

JavaScript
<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\DataAwareRule;
use Illuminate\Contracts\Validation\ValidationRule;

class DifferentFromEmail implements ValidationRule, DataAwareRule
{
    protected array $data = [];

    public function setData(array $data): static
    {
        $this->data = $data;

        return $this;
    }

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (isset($this->data['email']) && $value === $this->data['email']) {
            $fail('The :attribute must be different from the email.');
        }
    }
}

How do you translate the error message?

Call translate() on the object returned by $fail() to use a language file key instead of a hard-coded string. Check the validation documentation for the exact placeholder and locale options.

Code
$fail('validation.custom.username_weak')->translate();

How do you verify it works?

Write a small feature test that posts bad and good values and asserts the session errors.

JavaScript
public function test_username_rule_rejects_leading_digit(): void
{
    $this->post('/register', [
        'username' => '1admin',
        'email' => 'a@example.com',
        'password' => 'password123',
    ])->assertSessionHasErrors('username');
}

What are the common mistakes?

  • Ignoring non-string input, which breaks strlen() or preg_match()
  • Putting heavy database queries in a rule that runs on every request
  • Forgetting to return after the first $fail() when later checks would error
  • Relying on a rule class for security without a database unique constraint

FAQ

Does a custom rule run when the field is empty?

By default, rules do not run for empty values unless the field is implicit (for example required). Combine your rule with required when the field is mandatory.

Where should I store custom rules?

The default is app/Rules, which make:rule creates for you.

Rule class or closure?

Use a class when you reuse or unit test the logic. Use a closure for a single form.

References

Article Index & Topic Tags
LaravelPhpValidationForms

YouTube Channel

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Muhammad Yasir Amin
Available for Contracts
Author & Solutions Architect

Senior Full Stack Engineer

Independent Senior Full Stack Engineer working on Laravel, React and Next.js applications, APIs and delivery workflows.

Laravel & PHP ApplicationsReact & Next.js InterfacesREST APIs & IntegrationsDatabase Queries & DebuggingDocker Development WorkflowsClear Scope & Handover
Discuss Your Next Step

What Does Your Application Need Next?

Share your current system, the problem you want to solve and what a useful result looks like. We can identify the right starting point and agree on a practical scope.

Technical Audit

Find the Next Useful Fix

Application Development

Scoped, Reviewable Milestones

Ongoing Support

Agreed Capacity and Priorities

Share your goals, current stack and requirements before the call.