| name | laravel-validation |
| description | Complete guide to Laravel's Validator, validation rules, Form Requests, and custom rules. Use when writing or reviewing validation logic, choosing the right validation rule, building Form Request classes, creating custom rules, or working with conditional/array/nested validation. Triggers on validate(), Validator::make(), Form Request, Rule::, Password::, File::, required, nullable, unique, exists, or any Laravel validation rule usage.
|
Laravel Validation
Always use array syntax for rules, not pipe-delimited strings. Arrays are easier to read, diff, and extend — and required when using Rule objects or closures.
'email' => 'required|email|unique:users',
'email' => ['required', 'email', 'unique:users'],
Quick Decision Guide
"How do I validate?"
| Scenario | Approach |
|---|
| Simple controller validation | $request->validate([...]) |
| Reusable/complex validation | Form Request class |
| Outside HTTP request (CLI, jobs) | Validator::make($data, $rules) |
| Validate and get safe data | $request->safe() for ValidatedInput object |
| Validate but keep going | Validator::make()->validate() or check ->fails() |
"Which rule do I need?"
Presence & optionality:
| Need | Rule |
|---|
| Must be present and non-empty | required |
| Required only if present | sometimes |
| May be null | nullable |
| Must be present (even if empty) | present |
| Must not be in input | missing |
| Required when other field has value | required_if:field,value |
| Required when other field is absent | required_without:field |
| Exclude from validated output | exclude |
Type constraints:
| Need | Rule |
|---|
| String | string |
| Integer | integer |
| Numeric (int, float, string) | numeric |
| Boolean | boolean |
| Array | array |
| Sequential list (0-indexed) | list |
| Date | date or date_format:Y-m-d |
| File upload | file |
| Image | image |
| Email | email |
| URL | url |
| JSON | json |
| UUID | uuid |
Size & range:
| Need | Rule |
|---|
| Min/max length, value, count, or KB | min:n / max:n |
| Exact size | size:n |
| Between range | between:min,max |
| Compare to another field | gt:field / gte:field / lt:field / lte:field |
Database:
| Need | Rule |
|---|
| Must exist in table | exists:table,column or Rule::exists(...) |
| Must be unique in table | unique:table,column or Rule::unique(...) |
Validated Data Access
$validated = $request->validated();
$safe = $request->safe();
$safe->only(['name', 'email']);
$safe->except(['password']);
$safe->merge(['ip' => $request->ip()]);
Common Pitfalls
1. Missing nullable on optional fields
Laravel's ConvertEmptyStringsToNull middleware converts "" to null. Without nullable, empty optional fields fail validation.
'bio' => ['string', 'max:500'],
'bio' => ['nullable', 'string', 'max:500'],
2. sometimes vs nullable vs required
'name' => ['required', 'string'],
'nickname' => ['nullable', 'string'],
'email' => ['sometimes', 'required', 'email'],
3. Unsafe unique ignore
Never pass user-controlled input to ignore():
Rule::unique('users')->ignore($request->input('id'))
Rule::unique('users')->ignore($user->id)
4. bail placement
bail must be the first rule to stop processing on first failure:
'email' => ['required', 'bail', 'email', 'unique:users'],
'email' => ['bail', 'required', 'email', 'unique:users'],
5. Array vs nested validation confusion
'tags' => ['required', 'array', 'min:1'],
'tags.*' => ['string', 'max:50'],
'users.*.email' => ['required', 'email'],
'users.*.roles.*' => ['exists:roles,name'],
6. exists / unique with soft deletes
Rule::unique('users', 'email')->withoutTrashed()
Rule::exists('users', 'id')->whereNull('deleted_at')
7. Date comparison with field references
'end_date' => ['required', 'date', 'after:start_date'],
'start_date' => ['required', 'date', 'after:2024-01-01'],
'checkout' => ['required', 'date', 'after_or_equal:checkin'],
Rule Builders
Use fluent builders instead of string rules for complex validation. See references/rule-builders.md for the full API of every builder.
Password
use Illuminate\Validation\Rules\Password;
Password::defaults(fn () => Password::min(8)->uncompromised());
'password' => ['required', 'confirmed', Password::default()],
'password' => ['required', Password::min(8)
->letters()
->mixedCase()
->numbers()
->symbols()
->uncompromised(3)],
File
use Illuminate\Validation\Rules\File;
'document' => ['required', File::types(['pdf', 'docx'])->max('10mb')],
'avatar' => ['required', Rule::imageFile()->dimensions(Rule::dimensions()->maxWidth(2000))],
Enum
'status' => [Rule::enum(OrderStatus::class)->except([OrderStatus::Cancelled])],
Date & Numeric
'start_date' => [Rule::date()->afterToday()->format('Y-m-d')],
'price' => [Rule::numeric()->decimal(2)->min(0)->max(99999.99)],
'email' => [Rule::email()->rfcCompliant()->validateMxRecord()],
Conditional Validation
use Illuminate\Validation\Rule;
$request->validate([
'role' => ['required', 'string'],
// Required only when role is admin
'admin_code' => [Rule::requiredIf($request->role === 'admin')],
// Using closures for complex conditions
'company' => [Rule::requiredIf(fn () => $user->isBusinessAccount())],
'coupon' => ['exclude_if:type,free', 'required', 'string'],
]);
After validation hooks
$validator = Validator::make($data, $rules);
$validator->after(function ($validator) {
if ($this->somethingElseIsInvalid()) {
$validator->errors()->add('field', 'Something is wrong.');
}
});
Custom Rules
Rule object (recommended)
php artisan make:rule Uppercase
php artisan make:rule Uppercase --implicit
class Uppercase implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (strtoupper($value) !== $value) {
$fail('The :attribute must be uppercase.');
}
}
}
'name' => ['required', new Uppercase],
Closure rule (one-off)
'title' => [
'required',
function (string $attribute, mixed $value, Closure $fail) {
if ($value === 'foo') {
$fail("The {$attribute} is invalid.");
}
},
],
Custom Error Messages
$request->validate([
'email' => ['required', 'email'],
], [
'email.required' => 'We need your email address.',
'email.email' => 'That doesn\'t look like a valid email.',
]);
public function messages(): array
{
return [
'title.required' => 'A title is required.',
'body.required' => 'A message body is required.',
];
}
public function attributes(): array
{
return [
'email' => 'email address',
];
}
'photos.*.description.required' => 'Please describe photo #:position.',
Reference Files
- All validation rules: See references/rules.md for every built-in rule with signatures and descriptions
- Rule class & fluent builders: See references/rule-builders.md for
Rule::unique(), Rule::exists(), Password::, File::, Rule::date(), Rule::numeric(), Rule::email(), Rule::enum(), Rule::dimensions(), and all other fluent builder APIs
- Form Requests: See references/form-requests.md for Form Request patterns, methods, and advanced usage