Many Laravel validation rules are written as strings, with parameters after a colon split by commas. A rule like required_without:email works, but your editor cannot autocomplete it, and a misspelled rule name only fails when the validator runs.
The Rule class already had builders for rules like in, unique, exists, enum, date, email, and file. Laravel 13.36 adds one for every rule that was still string-only.
Before and After
Here is a profile form validated with string rules:
$request->validate([ 'email' => 'required_without:phone|email', 'phone' => 'required_without:email', 'website' => 'nullable|url:https', 'device_id' => 'uuid:4', 'tags.*' => 'distinct:ignore_case', 'password' => 'required|confirmed', 'timezone' => 'timezone:per_country,US',]);
The same rules with the new builders:
use Illuminate\Validation\Rule; $request->validate([ 'email' => [Rule::requiredWithout('phone'), 'email'], 'phone' => [Rule::requiredWithout('email')], 'website' => [Rule::nullable(), Rule::url()->protocols(['https'])], 'device_id' => [Rule::uuid()->version(4)], 'tags.*' => [Rule::distinct()->ignoreCase()], 'password' => [Rule::required(), Rule::confirmed()], 'timezone' => [Rule::timezone(['per_country', 'US'])],]);
String rules still work, and you can mix both styles in the same array.
Rules Without Parameters
These return the rule as a plain string, so Rule::required() is the string required. The full list of these include:
accepted()activeUrl()bail()base64()boolean()declined()exclude()filled()hexColor()json()list()macAddress()missing()nullable()present()prohibited()required()sometimes()ulid()
Passing true to Rule::boolean() returns boolean:strict, which accepts only true and false.
Rules That Take Fields or Values
These take the other field names, keys, or values as arguments, either as an array or as separate arguments:
Rule::requiredWith('first_name', 'last_name');Rule::requiredWith(['first_name', 'last_name']); Rule::acceptedIf('plan', 'pro');Rule::prohibits('coupon_code');Rule::requiredArrayKeys('street', 'city', 'postcode');
Here is the full set of rules:
acceptedIf()declinedIf()excludeWith()excludeWithout()inArray()inArrayKeys()missingIf()missingUnless()missingWith()missingWithAll()notRegex()presentIf()presentUnless()presentWith()presentWithAll()prohibitedIfAccepted()prohibitedIfDeclined()prohibits()regex()requiredArrayKeys()requiredIfAccepted()requiredIfDeclined()requiredWith()requiredWithAll()requiredWithout()requiredWithoutAll()timezone()
Rules With Options
These return an object with methods for the rule's options:
Rule::url()->protocols(['https']); // url:httpsRule::uuid()->version(7); // uuid:7Rule::ip()->version(6); // ipv6Rule::distinct()->strict()->ignoreCase(); // distinct:strict,ignore_caseRule::confirmed()->customField('repeat_pass'); // confirmed:repeat_passRule::currentPassword()->guard('admin'); // current_password:admin
The version() method on Rule::ip() accepts 4 or 6 and throws an InvalidArgumentException for any other number. On Rule::uuid(), version() also accepts the strings 'nil' and 'max'.
The fluent date builder gets an equals() method in the same release, which adds a date_equals rule:
Rule::date()->equals('2026-12-31');Rule::date()->equals(today());
Values With Commas
The string format splits parameters on commas. Suppose an accepted_if rule should apply when the plan field is Team, Annual:
'terms' => 'accepted_if:plan,Team, Annual',
The validator reads this as two values, Team and Annual, and the rule never matches Team, Annual.
The builders that take fields or values implement a new ParameterizedRule contract. Its toArray() method returns the rule name and its parameters as an array, and the validator uses that array directly instead of parsing a string. The value keeps its comma:
'terms' => [Rule::acceptedIf('plan', 'Team, Annual')],
This also covers quotes, pipes, and backslashes in values, and field or key names that contain commas, such as an array key for Rule::requiredArrayKeys().
Non-string values are converted to the strings the string format would use, so true, false, and null become 'true', 'false', and 'null':
Rule::presentIf('is_company', true); // present_if:is_company,true
If you cast a builder to a string, for example to log it, you get the string format with quotes around any value that contains a comma or a double quote.
A Form Request Example
A checkout form request might mix existing builders with the new ones:
use Illuminate\Foundation\Http\FormRequest;use Illuminate\Validation\Rule; class CheckoutRequest extends FormRequest{ public function rules(): array { return [ 'plan' => [Rule::required(), Rule::in(['starter', 'Team, Annual'])], 'terms' => [Rule::acceptedIf('plan', 'Team, Annual')], 'company_name' => [Rule::requiredIfAccepted('is_business')], 'vat_number' => [Rule::nullable(), Rule::requiredWith('company_name')], 'billing' => [Rule::required(), Rule::requiredArrayKeys('street', 'city', 'postcode')], 'coupon_code' => [Rule::nullable(), Rule::prohibits('gift_card')], 'website' => [Rule::nullable(), Rule::url()->protocols(['https'])], 'emails.*' => [Rule::distinct()->ignoreCase(), 'email'], ]; }}