Tech Verse Logo
Enable dark mode
Creating and Using Custom Validation Rules in Laravel

Creating and Using Custom Validation Rules in Laravel

Md. Mostafijur RahmanMMd. Mostafijur Rahman

Md. Mostafijur Rahman

•2 min read

When building Laravel applications, you’ll often run into situations where the default validation rules aren’t enough. For business logic that requires more control, Laravel allows you to create custom validation rules. These rules keep your validation logic clean, reusable, and easy to test.

php artisan make:rule ValidSlug

This creates a class that implements the ValidationRule contract. For example, to validate URL slugs:

namespace App\Rules;
 
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
 
class ValidSlug implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (!preg_match('/^[a-z0-9-]+$/', $value)) {
            $fail('The :attribute must only contain lowercase letters, numbers, and dashes.');
        }
    }
}

Applying Custom Rules

Using a custom rule looks just like applying a built-in rule:

use App\Rules\ValidSlug;
 
$request->validate([
    'slug' => ['required', new ValidSlug],
    'title' => ['required', 'string', 'max:255'],
]);

Example: Category Codes

Suppose you’re working on a blog where categories have fixed codes. You can enforce a format with a rule:

namespace App\Rules;
 
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
 
class CategoryCode implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (strlen($value) !== 3 || !ctype_alnum($value)) {
            $fail('The :attribute must be exactly 3 alphanumeric characters.');
        }
    }
}

Usage:

$request->validate([
    'name' => ['required', 'string', 'max:100'],
    'code' => ['required', new CategoryCode],
    'description' => ['nullable', 'string'],
]);

Parameterized Rules

Sometimes you need flexibility. You can pass arguments to rules when creating them:

namespace App\Rules;
 
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
 
class MinimumWordCount implements ValidationRule
{
    public function __construct(private int $minimumWords)
    {
    }
 
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $wordCount = str_word_count(strip_tags($value));
 
        if ($wordCount < $this->minimumWords) {
            $fail("The :attribute must contain at least {$this->minimumWords} words.");
        }
    }
}

Applying it:

$request->validate([
    'title' => ['required', 'string', 'max:200'],
    'content' => ['required', new MinimumWordCount(50)],
    'excerpt' => ['nullable', new MinimumWordCount(10)],
]);

Database-Aware Rules

Rules can also query the database. For example, checking whether an email is unique inside a department:

namespace App\Rules;
 
use Closure;
use App\Models\User;
use Illuminate\Contracts\Validation\ValidationRule;
 
class UniqueEmailInDepartment implements ValidationRule
{
    public function __construct(private int $departmentId)
    {
    }
 
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $exists = User::where('email', $value)
                     ->where('department_id', $this->departmentId)
                     ->exists();
 
        if ($exists) {
            $fail('This email address is already registered in your department.');
        }
    }
}

Testing Custom Rules

Since each rule is encapsulated in its own class, testing is straightforward:

class ValidSlugTest extends TestCase
{
    public function test_accepts_valid_slugs()
    {
        $rule = new ValidSlug;
        $failed = false;
 
        $rule->validate('slug', 'my-blog-post', function() use (&$failed) {
            $failed = true;
        });
 
        $this->assertFalse($failed);
    }
 
    public function test_rejects_invalid_slugs()
    {
        $rule = new ValidSlug;
        $failed = false;
 
        $rule->validate('slug', 'My Blog Post!', function() use (&$failed) {
            $failed = true;
        });
 
        $this->assertTrue($failed);
    }
}

Why Use Custom Rules?

  • Keeps controllers cleaner – no cluttered inline validation logic.

  • Encourages reuse – the same rule can be applied in multiple requests.

  • Easy to test – each rule has its own dedicated logic.

  • Great for complex business rules – especially when database queries are involved.

Md. Mostafijur RahmanMMd. Mostafijur Rahman

WRITTEN BY

Md. Mostafijur Rahman

    Latest Posts

    View All

    Safe Tenant Isolation in Laravel Multi Database Apps

    Safe Tenant Isolation in Laravel Multi Database Apps

    Handling Concurrent Writes with Laravel Pessimistic Locking

    Handling Concurrent Writes with Laravel Pessimistic Locking

    Hardening a Linux VPS with Custom Port and Keys

    Hardening a Linux VPS with Custom Port and Keys

    Next.js Server Actions Concurrency and Stale Data

    Next.js Server Actions Concurrency and Stale Data

    FastAPI Dependency Injection Scope Leaks in Production

    FastAPI Dependency Injection Scope Leaks in Production

    Mastering Next.js Streaming and Suspense

    Mastering Next.js Streaming and Suspense

    Preventing Race Conditions in Laravel Seeders

    Preventing Race Conditions in Laravel Seeders

    Linux Sysctl Tuning for High-Throughput Web Servers

    Linux Sysctl Tuning for High-Throughput Web Servers

    Local Embedding Models in Production RAG

    Local Embedding Models in Production RAG

    Preventing nextjs middleware cache poisoning

    Preventing nextjs middleware cache poisoning