{"id":650,"date":"2025-09-05T11:51:17","date_gmt":"2025-09-05T11:51:17","guid":{"rendered":"https:\/\/1v0.net\/blog\/?p=650"},"modified":"2025-09-05T11:51:20","modified_gmt":"2025-09-05T11:51:20","slug":"creating-custom-validation-rules-in-laravel","status":"publish","type":"post","link":"https:\/\/1v0.net\/blog\/creating-custom-validation-rules-in-laravel\/","title":{"rendered":"Creating Custom Validation Rules in Laravel"},"content":{"rendered":"\n<p>Laravel ships with a wide variety of validation rules, but sometimes your application requires domain-specific validation that doesn\u2019t exist out-of-the-box. Custom validation rules allow you to encapsulate business logic in a clean, reusable way. In this guide, you\u2019ll learn multiple approaches: using closures, creating dedicated Rule classes, and leveraging dependency injection. We\u2019ll also integrate custom rules into Blade forms, controllers, and feature tests to make sure everything is robust in production.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Approach 1: Inline Custom Rules with Closures<\/strong><\/h2>\n\n\n\n<p>For simple, one-off validations you can define a closure directly inside a <code>FormRequest<\/code> or controller. The closure receives the attribute, value, and a fail callback.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Http\/Requests\/RegisterUserRequest.php<\/span>\n<span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Requests<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Foundation<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">FormRequest<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">RegisterUserRequest<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">FormRequest<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">authorize<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">bool<\/span> <\/span>{ <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">true<\/span>; }\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">rules<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">array<\/span>\n    <\/span>{\n        <span class=\"hljs-keyword\">return<\/span> &#91;\n            <span class=\"hljs-string\">'username'<\/span> =&gt; &#91;\n                <span class=\"hljs-string\">'required'<\/span>,\n                <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">($attribute, $value, $fail)<\/span> <\/span>{\n                    <span class=\"hljs-keyword\">if<\/span> (str_contains(strtolower($value), <span class=\"hljs-string\">'admin'<\/span>)) {\n                        $fail(<span class=\"hljs-string\">'The '<\/span>.$attribute.<span class=\"hljs-string\">' may not contain \"admin\".'<\/span>);\n                    }\n                }\n            ],\n        ];\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This prevents reserved words from being used as usernames. While closures are quick, they can\u2019t be reused across multiple forms, so prefer dedicated rules for shared logic.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Approach 2: Custom Rule Classes<\/strong><\/h2>\n\n\n\n<p>Use Artisan to scaffold a new rule class:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">php artisan make:rule StrongPassword<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This generates <code>app\/Rules\/StrongPassword.php<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Rules<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Closure<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Contracts<\/span>\\<span class=\"hljs-title\">Validation<\/span>\\<span class=\"hljs-title\">ValidationRule<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">StrongPassword<\/span> <span class=\"hljs-keyword\">implements<\/span> <span class=\"hljs-title\">ValidationRule<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">validate<\/span><span class=\"hljs-params\">(string $attribute, mixed $value, Closure $fail)<\/span>: <span class=\"hljs-title\">void<\/span>\n    <\/span>{\n        <span class=\"hljs-keyword\">if<\/span> (strlen($value) &lt; <span class=\"hljs-number\">8<\/span>\n            || !preg_match(<span class=\"hljs-string\">'\/&#91;A-Z]\/'<\/span>, $value)\n            || !preg_match(<span class=\"hljs-string\">'\/&#91;0-9]\/'<\/span>, $value)) {\n            $fail(<span class=\"hljs-string\">'The '<\/span>.$attribute.<span class=\"hljs-string\">' must be at least 8 characters and contain an uppercase letter and number.'<\/span>);\n        }\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now apply it in a request:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Http\/Requests\/RegisterUserRequest.php<\/span>\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Rules<\/span>\\<span class=\"hljs-title\">StrongPassword<\/span>;\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">rules<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">array<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">return<\/span> &#91;\n        <span class=\"hljs-string\">'password'<\/span> =&gt; &#91;<span class=\"hljs-string\">'required'<\/span>, <span class=\"hljs-keyword\">new<\/span> StrongPassword],\n    ];\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Custom rule classes encapsulate logic in reusable components. They can be unit tested directly, keeping validation logic isolated and clean.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Approach 3: Dependency Injection in Rules<\/strong><\/h2>\n\n\n\n<p>Rules can access services via constructor injection. This is powerful for validating against external APIs or domain services.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Rules<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Services<\/span>\\<span class=\"hljs-title\">EmailBlacklistService<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Closure<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Contracts<\/span>\\<span class=\"hljs-title\">Validation<\/span>\\<span class=\"hljs-title\">ValidationRule<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">NotBlacklistedEmail<\/span> <span class=\"hljs-keyword\">implements<\/span> <span class=\"hljs-title\">ValidationRule<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">__construct<\/span><span class=\"hljs-params\">(protected EmailBlacklistService $service)<\/span> <\/span>{}\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">validate<\/span><span class=\"hljs-params\">(string $attribute, mixed $value, Closure $fail)<\/span>: <span class=\"hljs-title\">void<\/span>\n    <\/span>{\n        <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">$this<\/span>-&gt;service-&gt;isBlacklisted($value)) {\n            $fail(<span class=\"hljs-string\">'The email address is not allowed.'<\/span>);\n        }\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Bind the service in the container, then Laravel will inject it when instantiating the rule. This pattern is ideal for business-critical checks like fraud prevention.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Blade Form Integration<\/strong><\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">&lt;form method=<span class=\"hljs-string\">\"POST\"<\/span> action=<span class=\"hljs-string\">\"{{ route('register') }}\"<\/span>&gt;\n    @csrf\n    &lt;label&gt;Username&lt;\/label&gt;\n    &lt;input name=<span class=\"hljs-string\">\"username\"<\/span> value=<span class=\"hljs-string\">\"{{ old('username') }}\"<\/span> \/&gt;\n    @error(<span class=\"hljs-string\">'username'<\/span>) &lt;div <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span>=\"<span class=\"hljs-title\">error<\/span>\"&gt;<\/span>{{ $message }}&lt;\/div&gt; @enderror\n\n    &lt;label&gt;Password&lt;\/label&gt;\n    &lt;input type=<span class=\"hljs-string\">\"password\"<\/span> name=<span class=\"hljs-string\">\"password\"<\/span> \/&gt;\n    @error(<span class=\"hljs-string\">'password'<\/span>) &lt;div <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span>=\"<span class=\"hljs-title\">error<\/span>\"&gt;<\/span>{{ $message }}&lt;\/div&gt; @enderror\n\n    &lt;button type=<span class=\"hljs-string\">\"submit\"<\/span>&gt;Register&lt;\/button&gt;\n&lt;\/form&gt;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Custom validation rules behave exactly like built-in ones. Errors are displayed in Blade using <code>@error<\/code> blocks, and old values are preserved across submissions.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Testing Custom Rules<\/strong><\/h2>\n\n\n\n<p>Custom rules can be tested in isolation or via full feature tests. Here\u2019s a unit test for the <code>StrongPassword<\/code> rule:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ tests\/Unit\/StrongPasswordTest.php<\/span>\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Rules<\/span>\\<span class=\"hljs-title\">StrongPassword<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Support<\/span>\\<span class=\"hljs-title\">Facades<\/span>\\<span class=\"hljs-title\">Validator<\/span>;\n\ntest(<span class=\"hljs-string\">'validates strong password'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">()<\/span> <\/span>{\n    $rule = <span class=\"hljs-keyword\">new<\/span> StrongPassword();\n\n    $v = Validator::make(&#91;<span class=\"hljs-string\">'password'<\/span> =&gt; <span class=\"hljs-string\">'Weak'<\/span>], &#91;<span class=\"hljs-string\">'password'<\/span> =&gt; &#91;$rule]]);\n    expect($v-&gt;fails())-&gt;toBeTrue();\n\n    $v = Validator::make(&#91;<span class=\"hljs-string\">'password'<\/span> =&gt; <span class=\"hljs-string\">'StrongPass1'<\/span>], &#91;<span class=\"hljs-string\">'password'<\/span> =&gt; &#91;$rule]]);\n    expect($v-&gt;passes())-&gt;toBeTrue();\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This test ensures the rule works with both failing and passing values. For request-level validation, write <code>Feature<\/code> tests that submit forms and assert validation errors appear.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Best Practices for Custom Validation<\/strong><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Use <strong>closures<\/strong> for simple, one-off rules.<\/li>\n\n\n\n<li>Use <strong>dedicated rule classes<\/strong> for reusable or complex rules.<\/li>\n\n\n\n<li>Inject services into rules for external checks (databases, APIs, business rules).<\/li>\n\n\n\n<li>Always <strong>unit test<\/strong> your custom rules.<\/li>\n\n\n\n<li>Provide clear, user-friendly error messages.<\/li>\n\n\n\n<li>Avoid overloading rules; keep them focused on one responsibility.<\/li>\n<\/ul>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Wrapping Up<\/strong><\/h2>\n\n\n\n<p>Custom validation rules let you enforce domain-specific constraints in a clean and reusable way. We explored closures, dedicated rule classes, and injected rules. With proper Blade integration, user-friendly messages, and thorough testing, your forms will be both flexible and reliable.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>What\u2019s Next<\/strong><\/h2>\n\n\n\n<p>Continue learning about form handling and validation in Laravel:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/blog\/mastering-validation-rules-in-laravel-12\">Mastering Validation Rules in Laravel 12<\/a><\/li>\n\n\n\n<li><a href=\"\/blog\/building-a-multi-step-form-wizard-in-laravel\">Building a Multi-Step Form Wizard in Laravel<\/a><\/li>\n\n\n\n<li><a href=\"\/blog\/handling-file-uploads-and-image-storage-in-laravel\">Handling File Uploads and Image Storage in Laravel<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Laravel ships with a wide variety of validation rules, but sometimes your application requires domain-specific validation that doesn\u2019t exist out-of-the-box. Custom validation rules allow you to encapsulate business logic in a clean, reusable way. In this guide, you\u2019ll learn multiple approaches: using closures, creating dedicated Rule classes, and leveraging dependency injection. We\u2019ll also integrate custom [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":654,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[96,151,87,19],"class_list":["post-650","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-laravel","tag-blade","tag-rules","tag-testing","tag-validation"],"_links":{"self":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/650","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/comments?post=650"}],"version-history":[{"count":1,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/650\/revisions"}],"predecessor-version":[{"id":653,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/650\/revisions\/653"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media\/654"}],"wp:attachment":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media?parent=650"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/categories?post=650"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/tags?post=650"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}