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

Creating and Using Custom Validation Rules in Laravel

Tech Verse Daily

Tech Verse Daily

4 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.

    Latest Posts

    View All

    React 19: What’s new in React 19

    React 19: What’s new in React 19

    Laravel Strict Validation: Enforcing Exact PHP Types

    Laravel Strict Validation: Enforcing Exact PHP Types

    Next.js 16.0.1: The Essential Update Developers Shouldn’t Skip

    Next.js 16.0.1: The Essential Update Developers Shouldn’t Skip

    Time Interval Helpers in Laravel 12.40

    Time Interval Helpers in Laravel 12.40

    From GitHub Actions to Production Rollout: CI/CD for Laravel

    From GitHub Actions to Production Rollout: CI/CD for Laravel

    Top React Libraries and Frameworks Every Frontend Developer Should Know

    Top React Libraries and Frameworks Every Frontend Developer Should Know

    PHP 8.5 New Features and Deprecations

    PHP 8.5 New Features and Deprecations

    Manage, Track and Monitor Queue Jobs in Laravel with Vantage

    Manage, Track and Monitor Queue Jobs in Laravel with Vantage

    Tinkerwell 5: Introducing Tinkerwell Intelligence

    Tinkerwell 5: Introducing Tinkerwell Intelligence

    Roach PHP - Complete Web Scraping toolkit for PHP

    Roach PHP - Complete Web Scraping toolkit for PHP