logo
podcast Podcast
get help Get Unstuck

How to register custom validation rules as strings

It's a little trickier with newer versions of Laravel

Joel Clermont
Joel Clermont
2025-04-28

When you use one of the built-in validation rules that Laravel provides, you can use a string to refer to it in your code: 'required', 'email', and so on.

Last week, a question came up in the Mastering Laravel community about how to register a custom validation rule to work in the same way. I've never had a need to do it, but this dev was doing it for a package they built, and wanted the more native Laravel experience for the rule this package provides.

To provide a concrete example, let's say I have a custom PhoneNumber rule class and I want to access it as 'phone_number' in my validation rules.

If you're using the older (and now deprecated) Rule interface which has a passes() method on it, you can wire it up like this:

// app/Providers/AppServiceProvider.php

public function boot(): void
{
    $this->app['validator']->extend('phone_number', PhoneNumber::class . '@passes');
}

That's fairly straightforward, and the dev who asked the question already knew this. His question was more nuanced: How do you do this with the newer ValidationRule interface that uses the validate() method instead?

That is a bit trickier because this newer interface requires a $fail callback to be passed in. How should you define that? And how would you wire that up in the app service provider?

Before I show the solution, I want to say up front that there are a couple of things about it that I don't particularly like. And to keep this tip short, I won't get into them today, but stick with me. We will explore this topic in more depth throughout the week.

With that disclaimer out of the way, and after looking at how Laravel internally wires up these custom rules, here is the solution I came up with:

// app/Providers/AppServiceProvider.php

public function boot(): void
{
    $this->app['validator']->extend('phone_number', function($attribute, $value, $parameters, $validator) {
        return InvokableValidationRule::make(new PhoneNumber())
            ->setValidator($validator)
            ->passes($attribute, $value);
    });
}

This works! But wow, this is definitely more verbose than the previous solution. On top of that, why am I still calling the passes() method from the older, deprecated interface? And where is the $fail callback I said we needed?

Stay tuned throughout the week for a deeper dive into this topic, and all will be explained.

Here to help,

Joel

P.S. In my opinion, validation is one of the most under-appreciated aspects of Laravel. Check out Mastering Laravel Validation Rules to learn more.

Toss a coin in the jar if you found this helpful.
Want a tip like this in your inbox every weekday? Sign up below 👇🏼
email
No spam. Only real-world advice you can use.