Laravel Form Validation: Rules, Requests, and Custom Validation Techniques
This comprehensive guide explains Laravel's powerful form validation features, covering built‑in validation rules, quick validation examples, route and controller setup, handling validation errors, form request classes, custom error messages, conditional rules, array validation, and how to create custom validation rules using rule objects, closures, and extensions.
Form Validation Overview
Laravel provides several methods to validate incoming data. By default, the controller base class uses the ValidatesRequests trait, which offers a convenient way to apply a variety of powerful validation rules to HTTP requests.
Quick Validation
To demonstrate Laravel's validation capabilities, we will walk through a complete example that validates a form and displays error messages back to the user.
Defining Routes
Assume the following routes are defined in routes/web.php :
Route::get('post/create', 'PostController@create');
Route::post('post', 'PostController@store');The GET route shows a form for creating a new blog post, while the POST route stores the new post in the database.
Creating the Controller
Below is a skeleton controller with empty store method:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* Show the form for creating a blog post.
*/
public function create()
{
return view('post.create');
}
/**
* Save a new blog post.
*/
public function store(Request $request)
{
// Validation and storage logic will go here.
}
}Writing Validation Logic
Inside the store method we use the validate method provided by the Illuminate\Http\Request object. If validation passes, the code continues; if it fails, an exception is thrown and Laravel automatically generates an appropriate response (a redirect for normal requests or a JSON response with status 422 for AJAX requests).
$validatedData = $request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
// The blog post is now validated and can be saved.You can also use the pipe syntax to separate rules.
$validatedData = $request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);Bail Rule
To stop validation on the first failure for a given attribute, prepend the bail rule:
$request->validate([
'title' => 'bail|required|unique:posts|max:255',
'body' => 'required',
]);If the title fails the unique rule, the max rule will not be evaluated.
Validating Array Data
When a request contains nested parameters (arrays), you can validate them using dot notation:
$request->validate([
'title' => 'required|unique:posts|max:255',
'author.name' => 'required',
'author.description' => 'required',
]);Displaying Validation Errors
If validation fails, Laravel automatically redirects back to the previous page and flashes the error messages to the session. In Blade views you can access the $errors variable, which is an instance of Illuminate\Support\MessageBag :
@if ($errors->any())
@foreach ($errors->all() as $error)
{{ $error }}
@endforeach
@endif@error Directive
The @error Blade directive provides a shortcut to check for a specific field's error:
@error('title')
{{ $message }}
@enderrorOptional Fields
When a field may be null, add the nullable rule so that null is considered valid:
$request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'publish_at' => 'nullable|date',
]);AJAX Requests & Validation
For AJAX requests, the validate method returns a JSON response with status 422 containing the validation errors instead of performing a redirect.
Form Request Validation
For more complex validation scenarios, you can generate a dedicated form request class using the Artisan command:
php artisan make:request StoreBlogPostThe generated class resides in app/Http/Requests . Inside the rules method you define the validation rules:
public function rules()
{
return [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
];
}When this request class is type‑hinted in a controller method, Laravel automatically validates the incoming data before the method runs.
public function store(StoreBlogPost $request)
{
$validated = $request->validated();
// Store the blog post.
}If validation fails, the user is redirected back with errors stored in the session; AJAX requests receive a JSON response with status 422.
Adding Hooks After Validation
You may add a withValidator method to the form request to run additional logic after the validator is built but before it is evaluated:
public function withValidator($validator)
{
$validator->after(function ($validator) {
if ($this->somethingElseIsInvalid()) {
$validator->errors()->add('field', 'Something is wrong with this field!');
}
});
}Authorization
The authorize method determines whether the authenticated user may perform the request. Return true to allow, false to automatically return a 403 response.
public function authorize()
{
return $this->user()->can('update', $comment);
}Custom Error Messages
Override the messages method in the form request to provide custom messages for specific rules:
public function messages()
{
return [
'title.required' => 'A title is required',
'body.required' => 'A message is required',
];
}You can also define custom attribute names via the attributes method.
Manual Validator Creation
If you prefer not to use the validate method, you can manually create a validator instance using the Validator facade:
use Illuminate\Support\Facades\Validator;
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
if ($validator->fails()) {
return redirect('post/create')
->withErrors($validator)
->withInput();
}After validation you can retrieve errors via $validator->errors() and display them in the view.
Automatic Redirection
Even when you manually create a validator, you can call validate() on the instance to trigger Laravel's automatic redirection or JSON response:
Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
])->validate();Named Error Bags
When a page contains multiple forms, you can give each error bag a name using the second argument of withErrors and retrieve it via $errors->bag('login') in the view.
After Validation Hook
You may attach a callback with after to add additional errors after the main validation runs:
$validator = Validator::make($data, [
// rules
]);
$validator->after(function ($validator) {
if ($this->somethingElseIsInvalid()) {
$validator->errors()->add('field', 'Something is wrong with this field!');
}
});Conditional Rule Addition
Use the sometimes rule to apply validation only when the attribute is present in the input:
$v = Validator::make($data, [
'email' => 'sometimes|required|email',
]);For more complex conditions, pass a closure to sometimes that returns true when the extra rules should be applied:
$v->sometimes('reason', 'required|max:500', function ($input) {
return $input->games >= 100;
});Validating Arrays
Validate array fields using dot notation. Example for a single file inside an array:
$validator = Validator::make($request->all(), [
'photos.profile' => 'required|image',
]);Validate each element of an array using the * wildcard:
$validator = Validator::make($request->all(), [
'person.*.email' => 'email|unique:users',
'person.*.first_name' => 'required_with:person.*.last_name',
]);Custom Validation Rules
Rule Objects
Create a rule class with php artisan make:rule Uppercase . Implement the passes method to perform the check and the message method to return an error message.
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class Uppercase implements Rule
{
public function passes($attribute, $value)
{
return strtoupper($value) === $value;
}
public function message()
{
return 'The :attribute must be uppercase.';
}
}Use the rule in validation:
use App\Rules\Uppercase;
$request->validate([
'name' => ['required', 'string', new Uppercase],
]);Using Closures Directly
You can define an inline closure as a rule. The closure receives $attribute , $value , and a $fail callback:
$validator = Validator::make($request->all(), [
'title' => ['required', 'max:255', function ($attribute, $value, $fail) {
if ($value === 'foo') {
$fail($attribute . ' is invalid.');
}
}],
]);Extending the Validator
Register a custom rule via Validator::extend inside a service provider's boot method:
use Illuminate\Support\Facades\Validator;
public function boot()
{
Validator::extend('foo', function ($attribute, $value, $parameters, $validator) {
return $value == 'foo';
});
}Define a custom error message for the rule in the language file, placing it before the generic messages:
'foo' => 'Your input was invalid!',
'accepted' => 'The :attribute must be accepted.',
// ... other messagesFor implicit rules that should run even when the attribute is missing or empty, use Validator::extendImplicit or implement the Illuminate\Contracts\Validation\ImplicitRule interface on a rule object.
Implicit Rule Objects
Implement Illuminate\Contracts\Validation\ImplicitRule on a rule class to make it run when the attribute is empty.
Custom Placeholder Replacement
If you need custom placeholders in error messages, register a replacer via Validator::replacer in the service provider:
Validator::replacer('foo', function ($message, $attribute, $rule, $parameters) {
return str_replace(':custom', $parameters[0] ?? '', $message);
});Conclusion
Laravel's validation system offers a rich set of built‑in rules, flexible conditional validation, array handling, and multiple ways to create custom rules, making it straightforward to ensure data integrity for both simple forms and complex API requests.
Laravel Tech Community
Specializing in Laravel development, we continuously publish fresh content and grow alongside the elegant, stable Laravel framework.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.