Master Laravel validation by building custom rules that keep your controllers lean. Learn to write reusable, testable code for your PHP applications today.

Last month, I found myself staring at a StorePostRequest file that had grown to nearly 200 lines. I’d stuffed six different regex patterns and a complex database check right into the rules() method, and honestly, it was a nightmare to read.
If you’ve spent any time with Form validation in Laravel made easy: A Practical Guide, you know that keeping your controller clean is priority number one. But even with FormRequests, your validation logic can quickly become a dumping ground for messy, repetitive code. That’s where laravel validation techniques involving custom rules come in to save the day.
At first, I tried to handle everything using built-in rules like alpha_dash or exists. It worked fine until I needed to validate a specific SKU format that had to exist in a legacy inventory system. I ended up copying the same logic into three different controllers. When the SKU format changed, I had to hunt down every instance and update it manually.
That’s a classic sign that your code isn't as DRY (Don't Repeat Yourself) as it should be. By moving that logic into custom validation rules, you make your code testable, readable, and—most importantly—reusable across your entire application.

Laravel (version 8.x and later) makes this incredibly simple using the make:rule artisan command. Let’s say we want to validate that a password doesn't contain a user's name.
Run this in your terminal:
Bashphp artisan make:rule NoUserNameInPassword
This generates a class in app/Rules. Inside, you’ll see two main methods: validate() and message(). Here is how I’d implement that check:
PHPnamespace App\Rules; use Closure; use Illuminate\Contracts\Validation\ValidationRule; class NoUserNameInPassword implements ValidationRule { public function __construct(protected string $userName) {} public function validate(string $attribute, mixed $value, Closure $fail): void { if (str_contains(strtolower($value), strtolower($this->userName))) { $fail('The :attribute cannot contain your username.'); } } }
Now, you can use this rule in any FormRequest or controller validation array:
PHPuse App\Rules\NoUserNameInPassword; $request->validate([ 'password' => ['required', new NoUserNameInPassword($user->name)], ]);
It’s clean, it’s isolated, and it’s easy to write a unit test for this specific class without needing to boot up the entire HTTP stack.
You don't always need a full class for custom validation rules. If the logic is only used once, a closure is perfectly fine.
PHP$request->validate([ 'title' => [ 'required', function ($attribute, $value, $fail) { if (strtoupper($value) !== $value) { $fail('The '.$attribute.' must be uppercase.'); } }, ], ]);
However, I generally stick to the "Rule of Three." If I find myself copying a closure into a second file, it immediately gets promoted to a dedicated class. This keeps my PHP development workflow disciplined and prevents technical debt from piling up in my request classes.
Sometimes, your validation needs to interact with the database. Suppose you're building a booking system and need to ensure a slot isn't already taken. Instead of putting a raw DB::table() call in your controller, you can inject a repository or use Eloquent directly inside your custom rule.
PHPpublic function validate(string $attribute, mixed $value, Closure $fail): void { if (Appointment::where('start_time', $value)->exists()) { $fail('This time slot is already booked.'); } }
This approach is much cleaner than the alternative. If you're interested in keeping your application architecture sound, you might also want to look into Mastering Laravel Policies: A Practical Guide to Authorization Logic to ensure your authorization logic stays just as separated as your validation logic.

One thing I’ve learned the hard way: don't over-engineer simple rules. If you can achieve your goal with a combination of regex or existing rules, do that first. Custom rules add a layer of abstraction that, while helpful, does increase the number of files in your project.
Also, remember that these rules are executed after the initial request is received, so ensure your dependencies are handled via the constructor if you need to access services or repositories.
Are custom rules slower than built-in rules? Technically, yes, because of the extra class instantiation, but in a real-world application, the performance impact is usually negligible (often well under 1ms). Don't optimize until you have a real bottleneck.
Can I use custom rules in API requests? Absolutely. In fact, using them alongside Mastering Laravel API Resources: A Guide to Clean JSON Responses is the gold standard for maintaining a professional API.
How do I test these rules?
Since they implement ValidationRule, you can instantiate them in a standard PHPUnit test and pass a closure to the $fail parameter to assert that your logic triggers the error when expected.
I’m still experimenting with ways to make these rules even more fluent. Sometimes I wonder if I should be using trait-based validation for common domain-specific checks, but for now, the dedicated rule class remains my go-to for keeping things maintainable. Happy coding!
Mastering Laravel queues helps you move slow tasks to the background. Learn how to implement background jobs to boost your PHP performance and app reliability.