Validating Strings and Numbers in Laravel
Most validation work comes down to two questions: “is this text acceptable?” and “is this number in range?” The rules in this guide answer both. They cover the everyday cases (a name, a price, an email address) along with the awkward ones that show up once your form goes to production: a value that arrives as a string instead of a number, a Unicode character that breaks an ASCII regex, an MX check that needs a PHP extension nobody installed.
Accepting Text Input
The string rule rejects anything that is not a PHP string. Arrays, integers, and booleans all fail:
$request->validate([
'title' => ['required', 'string', 'max:255'],
'bio' => ['nullable', 'string', 'max:1000'],
]);
When a field can legitimately arrive as null (a JSON body, for instance), pair string with nullable. Without it, null fails the string check before any other rule runs.
An exact character count uses size:
'country_code' => ['required', 'string', 'size:2'], // "US", "DE"
'otp' => ['required', 'string', 'size:6'], // "482910"
For a length range, between is shorter than writing both min and max:
'username' => ['required', 'string', 'between:3,30'],
If the field might hold either a string or an integer (a polymorphic ID from a frontend), skip the string rule entirely and validate each possibility with a custom rule:
// Accept either a numeric ID or a UUID slug
'reference' => ['required', function ($attribute, $value, $fail) {
if (!is_string($value) && !is_int($value)) {
$fail('The reference must be a string or integer.');
}
}],
When a field may arrive as either a string or an array (a tag input that sends a single value or a list), the cleanest approach is to normalize in prepareForValidation() and then validate a single type:
protected function prepareForValidation(): void
{
if (is_string($this->tags)) {
$this->merge(['tags' => [$this->tags]]);
}
}
public function rules(): array
{
return [
'tags' => ['required', 'array', 'max:10'],
'tags.*' => ['string', 'max:50'],
];
}
This avoids having to branch on type at the validation layer. The input always becomes an array before rules run.
The contains rule checks whether an array includes specific values. It has nothing to do with substring matching on strings. For “string contains substring” checks, use regex.
The Fluent String Builder
Laravel 13 provides Rule::string() with chainable constraints:
use Illuminate\Validation\Rule;
'username' => [
'required',
Rule::string()
->min(3)
->max(30)
->alphaDash(ascii: true),
],
Available methods: alpha, alphaDash, alphaNumeric, ascii, between, doesntEndWith, doesntStartWith, endsWith, exactly, lowercase, max, min, startsWith, uppercase. The builder is conditionable, so when() and unless() work too:
Rule::string()
->min(2)
->when($requireAscii, fn ($rule) => $rule->ascii()),
Alpha, AlphaDash, and AlphaNum
These three rules control which characters are allowed. By default all three are Unicode-aware, meaning accented characters like ñ, ü, or Cyrillic letters pass:
| Rule | Allows | Example passes |
|---|---|---|
alpha | Letters only ([\p{L}], [\p{M}]) | François |
alpha_dash | Letters, digits, dashes, underscores | vue-router_2 |
alpha_num | Letters and digits only | Room4B |
To restrict to ASCII range (a-z, A-Z, 0-9), append :ascii:
'slug' => 'required|alpha_dash:ascii|max:80',
'username' => 'required|alpha_num:ascii|between:3,20',
None of these rules accept spaces. A “name” field that should allow letters and spaces needs a different approach:
// regex that allows Unicode letters, spaces, hyphens, and apostrophes
'name' => ['required', 'string', 'max:100', 'regex:/^[\p{L}\s\-\']+$/u'],
For slugs and machine identifiers, alpha_dash:ascii is the standard choice. It permits exactly the characters found in URL slugs and database column names:
'slug' => 'required|alpha_dash:ascii|between:2,80',
'api_key' => 'required|alpha_num:ascii|size:32',
'short_code' => 'required|alpha:ascii|between:2,5|uppercase',
Integers, Numerics, and Digits
These three rules look similar but solve different problems, and picking the wrong one causes the most common number-validation bugs.
integer uses PHP’s FILTER_VALIDATE_INT. It accepts "42" (a string containing an integer) by default. It rejects 3.14, "3.14", and empty strings. Use integer:strict to reject string integers:
'quantity' => 'required|integer|min:1',
// API that must receive an actual int type, not "5"
'count' => 'required|integer:strict|min:0',
numeric uses PHP’s is_numeric(). It accepts integers, floats, and their string representations: 42, "42", 3.14, "3.14", "-0.5". Use numeric:strict to accept only int/float types:
'price' => 'required|numeric|min:0.01|max:999999.99',
// The field arrives as a proper float from a typed JSON body
'latitude' => 'required|numeric:strict|between:-90,90',
digits:N checks that the value is an integer with exactly N digits. It validates string length, not numeric value:
'zip' => 'required|digits:5', // "02134" passes, "2134" fails
'pin' => 'required|digits_between:4,6', // 4, 5, or 6 digits
Related digit rules: max_digits:N caps the digit count, min_digits:N sets a floor:
'account_number' => 'required|digits_between:8,12',
'routing_number' => 'required|digits:9',
'invoice_number' => 'required|integer|max_digits:10',
Nullable Numbers
Numeric fields from optional form inputs often arrive as null or as an empty string. Use nullable to let null pass through, and remember that empty strings are converted to null by Laravel’s ConvertEmptyStringsToNull middleware:
'discount' => ['nullable', 'numeric', 'between:0,100'],
'max_weight' => ['nullable', 'integer', 'min:1'],
The nullable rule allows the value to be null, but the field itself must still be present in the request. If the field can be omitted entirely, sometimes is the right tool – it tells Laravel to skip validation when the key is missing. The two are often combined: ['sometimes', 'nullable', 'numeric'] means “if you send it, it can be null or a number; if you do not send it, that is fine too.”
For API endpoints where a field can hold either an integer or a decimal (a generic “number” input), numeric covers both:
'threshold' => ['required', 'numeric', 'min:0'], // accepts 5, 5.0, "5.5"
When to Use Which
| Scenario | Rule | Why |
|---|---|---|
| Quantity, age, page number | integer|min:1 | Whole numbers only |
| Price, weight, coordinates | numeric|between:... | Allows decimals |
| ZIP code, PIN, OTP | digits:N or digits_between:N,M | Exact digit length matters |
| Precise decimal places (currency) | decimal:2 or decimal:2,4 | Controls fraction part |
| Positive integer, no zero | integer|min:1 | gt:0 also works but needs type match |
The strict variants (integer:strict, numeric:strict) are useful for typed JSON APIs where the client sends actual numbers, not string representations. Without strict mode, "42" and 42 both pass integer. With it, only 42 passes. This distinction matters when your API contract specifies types and you want to enforce them at the validation layer rather than relying on implicit casting later.
A common confusion: numeric accepts "1,500" in some locales? No. PHP’s is_numeric() rejects comma-separated numbers. Input like "1,500" or "3,5" (European decimal) will fail. Strip commas or convert the separator before validation:
// In a FormRequest's prepareForValidation()
protected function prepareForValidation(): void
{
if (is_string($this->price)) {
$this->merge([
'price' => str_replace(',', '.', $this->price),
]);
}
}
Constraining Length and Value
The min, max, size, and between rules change meaning based on the type of the field:
| Type | min:5 means | max:100 means |
|---|---|---|
string | at least 5 characters | at most 100 characters |
numeric / integer | value >= 5 | value <= 100 |
array | at least 5 elements | at most 100 elements |
file | at least 5 KB | at most 100 KB |
Without an explicit type rule, Laravel guesses the type from the input. This leads to bugs: a field containing "42" might be treated as numeric one request and as a string the next, depending on whether other rules hint at the type. Always pair size rules with a type rule:
// Ambiguous: does max:255 mean 255 characters or numeric value 255?
'code' => 'required|max:255',
// Clear: 255 characters
'code' => 'required|string|max:255',
The size rule checks for an exact match:
'state' => 'required|string|size:2', // exactly 2 chars
'answer' => 'required|integer|size:42', // exactly 42
'coordinates' => 'required|array|size:2', // exactly 2 elements
between is inclusive on both ends:
'age' => 'required|integer|between:18,120',
'rating' => 'required|numeric|between:0,5',
'title' => 'required|string|between:10,200',
gt, gte, lt, lte: Comparing Against Another Field
These rules compare the current field to another field’s value (or to a literal value). Both fields must be of the same type:
$request->validate([
'min_price' => 'required|numeric|min:0',
'max_price' => 'required|numeric|gt:min_price',
'start_qty' => 'required|integer|min:1',
'end_qty' => 'required|integer|gte:start_qty',
]);
For a simple “greater than zero” check without a second field, min:1 (for integers) or gt:0 (for numerics) does the job:
'amount' => 'required|numeric|gt:0', // must be positive, decimals OK
'seats' => 'required|integer|min:1', // must be at least 1
A practical difference between gt and min: gt:0 fails on exactly 0 (strictly greater), while min:0 allows 0 (greater than or equal). For “the value must not be zero or negative”, gt:0 is the right choice. For “zero is fine, negative is not”, use min:0.
Capping the maximum numeric value works the same way:
'percentage' => 'required|integer|between:0,100',
'latitude' => 'required|numeric|between:-90,90',
'year' => 'required|integer|min:1900|max:2100',
Email Validation
The email rule defaults to RFC validation only. An address like user@localhost passes because it is technically valid per the RFC spec. For production forms, add more validators:
// Basic: RFC check only
'email' => 'required|email',
// Recommended: RFC + working MX record
'email' => 'required|email:rfc,dns',
// Paranoid: RFC + DNS + Unicode spoof protection
'email' => 'required|email:rfc,dns,spoof',
The available validators:
| Validator | What it checks | Needs |
|---|---|---|
rfc | Syntax per RFC (default) | nothing extra |
strict | RFC with no warnings (rejects trailing dots, consecutive dots) | nothing extra |
dns | Domain has a valid MX record | PHP intl extension |
spoof | No homograph/deceptive Unicode characters | PHP intl extension |
filter | PHP’s filter_var check | nothing extra |
filter_unicode | filter_var allowing Unicode local parts | nothing extra |
The fluent builder reads more clearly when stacking multiple validators:
use Illuminate\Validation\Rule;
'email' => [
'required',
Rule::email()
->rfcCompliant(strict: false)
->validateMxRecord()
->preventSpoofing(),
],
For a newsletter signup where you just need a working inbox, email:rfc,dns catches the worst typos without slowing things down. For a login form, email:rfc alone is fine because the address already exists in your database. For a payment form that must prevent fraud, add spoof to catch Unicode lookalike domains.
One thing to watch: dns and spoof require PHP’s intl extension. Many Docker images and shared hosting environments do not install it by default. If the extension is missing, email:rfc,dns throws a LogicException at runtime, not a graceful fallback. Verify with php -m | grep intl before deploying rules that rely on DNS or spoof checks.
To ensure the email is unique in a database table, chain it with the unique rule:
'email' => 'required|email:rfc,dns|unique:users,email',
Email or Phone
There is no built-in “email or phone” rule. Handle it with a closure that runs each validator and accepts the input if either passes:
use Illuminate\Support\Facades\Validator;
'contact' => ['required', function ($attribute, $value, $fail) {
$asEmail = Validator::make(
['contact' => $value],
['contact' => 'email:rfc']
);
$asPhone = preg_match('/^\+?[1-9]\d{6,14}$/', (string) $value);
if ($asEmail->fails() && !$asPhone) {
$fail('Provide a valid email address or phone number.');
}
}],
Routing the email branch through Laravel’s own validator keeps the logic consistent with the rest of the form. If you ever switch the email rule (say, to add dns), the closure picks up the change automatically.
URL and UUID
url checks whether the value is a well-formed URL. By default it accepts any scheme. To restrict to web URLs:
'website' => 'required|url:http,https',
A common question: how to accept a URL without the http:// prefix? The url rule requires a scheme, so you have two options. Prepend the scheme before validation, or use a regex:
// Option A: prepend in prepareForValidation
protected function prepareForValidation(): void
{
if ($this->website && !preg_match('#^https?://#', $this->website)) {
$this->merge(['website' => 'https://' . $this->website]);
}
}
// Option B: regex that accepts with or without scheme
'website' => ['required', 'regex:/^(https?:\/\/)?[\w\-]+(\.[\w\-]+)+[\/\w\-\.~:?#\[\]@!$&\'()*+,;=%]*$/'],
active_url goes further: it extracts the hostname with parse_url() and looks up an A or AAAA record via dns_get_record(). This catches typos like gogle.com but adds DNS-lookup latency and can fail for internal hostnames or during outages. Reserve it for public-facing URL fields where reachability actually matters.
You can also restrict to HTTPS-only in production while allowing HTTP in local development:
'webhook_url' => [
'required',
app()->isProduction() ? 'url:https' : 'url:http,https',
],
uuid validates RFC 9562 UUIDs (versions 1, 3, 4, 5, 6, 7, 8). To restrict to a specific version:
'token' => 'required|uuid', // any version
'public_id' => 'required|uuid:4', // v4 only
'sort_id' => 'required|ulid', // ULID format instead
Use uuid:4 if your system generates v4 UUIDs and you want to reject v1 (time-based) or v7 (sortable) IDs that might come from other systems. Use bare uuid when accepting IDs from third-party integrations where the version is not under your control.
Regex Patterns
The regex and not_regex rules run the value through PHP’s preg_match(). The pattern must include delimiters:
'slug' => ['required', 'regex:/^[a-z0-9]+(-[a-z0-9]+)*$/'],
If the pattern contains a pipe character |, you must use array syntax. The string syntax breaks because | is also the rule separator:
// Broken: Laravel sees three separate rules
'code' => 'required|regex:/^(AB|CD|EF)\d+$/',
// Fixed: array syntax
'code' => ['required', 'regex:/^(AB|CD|EF)\d+$/'],
Special characters (anchors, quantifiers, Unicode classes) work the same as in raw PHP:
// Unicode letters and spaces, 2-50 chars
'name' => ['required', 'regex:/^[\p{L}\s]{2,50}$/u'],
// Hex color code
'color' => ['required', 'regex:/^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/'],
// No consecutive spaces
'comment' => ['required', 'string', 'not_regex:/\s{2,}/'],
To set a custom error message for a regex rule, reference it by attribute and rule name:
$request->validate(
['slug' => ['required', 'regex:/^[a-z0-9\-]+$/']],
['slug.regex' => 'The slug may only contain lowercase letters, numbers, and hyphens.']
);
Regex error messages default to “The {field} format is invalid”, which tells the user nothing about what went wrong. Always provide a custom message that describes the expected format.
not_regex is the inverse – it rejects values that match the pattern. Useful for blocking unwanted input:
// Block HTML tags in a plain-text bio
'bio' => ['required', 'string', 'max:500', 'not_regex:/<[^>]+>/'],
// Reject strings that are only whitespace
'title' => ['required', 'string', 'not_regex:/^\s+$/'],
For complex validation logic where a single regex is hard to read, consider a custom rule class instead. A regex that needs a comment to explain itself is probably better as a named rule.
Phone Number Validation
Laravel has no built-in phone rule, so the choice is between writing a regex yourself or pulling in a package.
Regex works for a single expected format:
// E.164 international format: +1234567890 (7-15 digits after +)
'phone' => ['required', 'regex:/^\+[1-9]\d{6,14}$/'],
// North American: (555) 123-4567 or 555-123-4567
'phone' => ['required', 'regex:/^(\(\d{3}\)\s?|\d{3}[-.])\d{3}[-.]?\d{4}$/'],
A package handles international formats, carrier validation, and formatting. The most popular is propaganistas/laravel-phone:
composer require propaganistas/laravel-phone
use Propaganistas\LaravelPhone\Rules\Phone;
'mobile' => ['required', (new Phone)->mobile()->country(['US', 'GB', 'DE'])],
The package validates against Google’s libphonenumber library, which covers number plans for every country. For any form accepting international phone numbers, a regex alone is not enough – country-specific formatting rules, variable length, and carrier prefixes make it impractical.
A middle ground: validate the format loosely on the backend and verify the number exists by sending an SMS or calling a verification API (Twilio, Vonage). This separates “looks like a phone number” from “is a real phone number”:
// Loose format check, then verify asynchronously
'phone' => ['required', 'string', 'min:7', 'max:16', 'regex:/^\+?\d[\d\s\-()]+$/'],
Boolean as String
HTML checkboxes submit "1" or nothing. The boolean rule accepts true, false, 1, 0, "1", and "0":
'newsletter' => 'boolean',
'terms' => 'accepted', // must be "yes", "on", 1, "1", true, or "true"
When you need the field to be an actual true/false (typed JSON, for example), use the strict mode:
'is_active' => 'required|boolean:strict',
A common trap with checkboxes: browsers do not send unchecked checkboxes at all. The field will be absent from the request, not false. Use sometimes or set a default in prepareForValidation():
// Option A: only validate if present
'newsletter' => 'sometimes|boolean',
// Option B: ensure it always has a value
protected function prepareForValidation(): void
{
$this->mergeIfMissing(['newsletter' => false]);
}
Decimal Precision
The decimal rule enforces exact decimal places:
'price' => 'required|decimal:2', // 9.99 OK, 9.9 fails
'measurement' => 'required|decimal:1,4', // 9.5 to 9.1234 OK
'percentage' => 'required|decimal:0,2', // 100 or 99.5 or 99.55 OK
This pairs well with numeric or between to cap the overall value:
'rate' => 'required|numeric|decimal:2|between:0.01,999.99',
For currency stored as integers (cents), skip decimal and validate as an integer:
'amount_cents' => 'required|integer|min:1|max:99999999',
Other String and Number Checks
Several rules handle specific formats that do not fit the categories above.
Case rules enforce lowercase or uppercase on the entire value:
'slug' => 'required|string|lowercase|alpha_dash:ascii',
'country_code' => 'required|string|uppercase|size:2',
'locale' => 'required|string|lowercase|size:2',
Prefix and suffix checks avoid regex for simple cases:
'callback_url' => ['required', 'string', 'starts_with:https://'],
'filename' => ['required', 'string', 'doesnt_end_with:.exe,.bat,.sh'],
'ref' => ['required', 'string', 'ends_with:-ref'],
Network and identifier formats:
'server_ip' => 'required|ip', // IPv4 or IPv6
'gateway' => 'required|ipv4', // IPv4 only
'device_mac' => 'required|mac_address',
'config' => 'required|json', // valid JSON string
'theme_color' => 'required|hex_color', // #fff or #a1b2c3
'csv_row' => 'required|ascii', // 7-bit ASCII only
multiple_of verifies divisibility, useful for pack sizes, seating, and pagination:
'pack_size' => 'required|integer|multiple_of:6', // 6, 12, 18...
'page_size' => 'required|integer|multiple_of:10', // 10, 20, 30...
'seat_count' => 'required|integer|multiple_of:2', // pairs only
Recipes
Product Form
public function rules(): array
{
return [
'name' => ['required', 'string', 'between:2,200'],
'sku' => ['required', 'alpha_dash:ascii', 'max:40', 'unique:products,sku'],
'price' => ['required', 'numeric', 'decimal:2', 'between:0.01,99999.99'],
'weight_kg' => ['nullable', 'numeric', 'min:0.001', 'max:1000'],
'quantity' => ['required', 'integer', 'min:0', 'max:999999'],
'category_id' => ['required', 'integer', 'exists:categories,id'],
'tags' => ['nullable', 'array', 'max:10'],
'tags.*' => ['string', 'alpha_dash:ascii', 'max:30'],
];
}
Registration Form with Phone or Email
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:100', 'regex:/^[\p{L}\s\-\']+$/u'],
'email' => ['required', 'email:rfc,dns', 'unique:users,email'],
'phone' => ['nullable', 'regex:/^\+[1-9]\d{6,14}$/'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
'age' => ['required', 'integer', 'between:13,120'],
'website' => ['nullable', 'url:http,https', 'max:255'],
];
}
API Settings Endpoint
$validated = $request->validate([
'display_name' => ['required', Rule::string()->min(2)->max(50)],
'locale' => ['required', 'string', 'size:2', 'lowercase'],
'timezone' => ['required', 'string', 'timezone:all'],
'page_size' => ['required', 'integer', 'between:10,100', 'multiple_of:10'],
'is_public' => ['required', 'boolean:strict'],
'avatar_url' => ['nullable', 'url:https', 'max:500'],
'bio' => ['nullable', 'string', 'max:500'],
]);
Price Range Filter
$request->validate([
'min_price' => ['nullable', 'numeric', 'min:0'],
'max_price' => ['nullable', 'numeric', 'gt:min_price'],
'currency' => ['required', 'string', 'uppercase', 'size:3'],
]);
Geocoordinates
$request->validate([
'lat' => ['required', 'numeric', 'between:-90,90'],
'lng' => ['required', 'numeric', 'between:-180,180'],
'radius_km' => ['nullable', 'numeric', 'gt:0', 'max:500'],
'label' => ['nullable', 'string', 'max:100'],
]);
Bulk Import Row
A single row from a CSV import, where every field arrives as a string and needs both type validation and business-logic constraints:
public function rules(): array
{
return [
'rows' => ['required', 'array', 'min:1', 'max:1000'],
'rows.*.sku' => ['required', 'alpha_dash:ascii', 'max:40'],
'rows.*.name' => ['required', 'string', 'between:2,200'],
'rows.*.price' => ['required', 'numeric', 'decimal:2', 'min:0.01'],
'rows.*.quantity' => ['required', 'integer', 'min:0'],
'rows.*.weight' => ['nullable', 'numeric', 'min:0'],
'rows.*.country' => ['required', 'string', 'uppercase', 'size:2'],
'rows.*.email' => ['nullable', 'email:rfc'],
'rows.*.notes' => ['nullable', 'string', 'max:500'],
];
}
Common Mistakes
1. Missing type rule before min/max. The field 'age' => 'required|min:18' treats "25" as a 2-character string, not the number 25. Add integer:
'age' => 'required|integer|min:18',
2. Using digits to cap numeric value. digits:5 means exactly 5 digits long, not “up to 99999”. For a value cap, use integer|max:99999.
3. Expecting numeric to accept commas. Input "1,500.00" fails numeric. Strip formatting in prepareForValidation().
4. Regex without array syntax. Any regex containing | must use array notation, otherwise Laravel splits the rule at the pipe.
5. Email dns without intl extension. The dns and spoof validators need PHP’s intl extension. Without it, a LogicException is thrown at runtime. Check with php -m | grep intl before deploying.
6. active_url in tests. DNS lookups fail for fake domains in test environments. Mock the validator or use url for tests and active_url only in production rules.
7. Alpha rules rejecting accented names. alpha:ascii rejects José and Müller. Drop the :ascii option or switch to a regex that allows the characters you need.
8. Confusing size with max. size:10 means exactly 10 (characters, value, elements). For “up to 10”, use max:10.
9. Using numeric where integer is needed. A quantity field validated with numeric accepts 2.5, which makes no sense for item counts. Use integer for fields that must be whole numbers.
10. Forgetting decimal needs a numeric value. decimal:2 enforces decimal places but does not itself check that the input is a number. Combine it with numeric:
'price' => 'required|numeric|decimal:2|min:0',
Testing Validation Rules
Laravel’s test helpers make it straightforward to verify that your validation rules work as expected:
public function test_product_price_must_be_positive_decimal(): void
{
$this->post('/products', ['price' => '-5.00'])
->assertSessionHasErrors('price');
$this->post('/products', ['price' => '0'])
->assertSessionHasErrors('price');
$this->post('/products', ['price' => '19.99', /* other required fields */])
->assertSessionDoesntHaveErrors('price');
}
For JSON APIs, use assertJsonValidationErrors:
$this->postJson('/api/settings', ['page_size' => 15])
->assertJsonValidationErrors(['page_size']);
$this->postJson('/api/settings', ['page_size' => 20])
->assertJsonMissingValidationErrors(['page_size']);
When testing regex and email rules with edge cases, test the boundary values that real users will submit:
// Edge cases for email validation
$this->postJson('/api/register', ['email' => 'user@localhost'])
->assertJsonValidationErrors(['email']); // fails dns check
$this->postJson('/api/register', ['email' => '[email protected]'])
->assertJsonMissingValidationErrors(['email']);
Quick Reference
| Need | Rule combination |
|---|---|
| Required text, capped length | required|string|max:255 |
| Optional text | nullable|string|max:N |
| Username (ASCII, dashes OK) | required|alpha_dash:ascii|between:3,20 |
| Whole number, positive | required|integer|min:1 |
| Decimal price | required|numeric|decimal:2|min:0.01 |
| Value in range | required|integer|between:1,100 |
| One field > another | required|numeric|gt:other_field |
| Email (production) | required|email:rfc,dns |
| URL (web only) | required|url:http,https |
| Phone (international) | required|regex:/^\+[1-9]\d{6,14}$/ |
| UUID v4 | required|uuid:4 |
| Exact string length | required|string|size:N |
| JSON payload | required|json |
Cross-references:
- Validation Basics for the overall validation flow
- Form Requests for
prepareForValidation()and organized rule sets - Custom Rules for validation logic beyond built-in rules
- Error Messages for customizing what users see when validation fails
- Arrays and JSON for validating nested structures
- Conditional Rules for
required_if,sometimes, and dynamic rule sets - Dates for date and time validation
- Unique and Exists for database-backed checks