In Laravel validation, how do required_if, exclude_if and Rule::when differ when a field depends on another field's value?
answer
- require it, drop it, or swap rules
- required_if:account_type,business
- exclude_if removes it from validated()
- put exclude_if first; rules run in order
- Rule::when(cond, rules, default)
basics
~20 srequired_if makes a field mandatory when another field has a value but still validates and keeps it otherwise. exclude_if skips the field entirely and drops it from validated(). Rule::when picks one of two rule sets from a boolean or closure evaluated at validation time.
solid answer
~40 s`required_if:account_type,business` makes `company_name` required only for business accounts; for personal accounts it may be omitted, but if sent its other rules still run and it still lands in `validated()`. `exclude_if:account_type,personal` goes further: when the condition holds, the field is removed from the data and none of its rules run, so it never reaches `validated()`. Rules run in the order written, so `exclude_if` must come first. `Rule::when($condition, $rules, $default)` chooses between two rule lists; the condition may be a boolean or a closure receiving the input as a `Fluent`. `Rule::requiredIf()` and `Rule::excludeIf()` accept a closure when a string rule cannot express the condition.
code
php · 14 lines<?php
use Illuminate\Validation\Rule;
$rules = [
'account_type' => ['required', Rule::in(['personal', 'business'])],
'company_name' => ['required_if:account_type,business', 'nullable', 'string', 'max:100'],
'vat_number' => ['exclude_if:account_type,personal', 'required', 'string', 'size:11'],
'website' => Rule::when(
fn ($input) => $input->account_type === 'business',
['required', 'url'],
['nullable', 'url'],
),
];go deeper
Recognise required_if for fields that are mandatory only in some cases.
Contrast required_if with exclude_if, including what reaches validated(), and write Rule::when with a default rule set.
Place exclusion rules first, handle boolean comparisons from forms versus JSON, and prevent stale hidden values being stored.
Decide how much conditional logic belongs in validation versus a dedicated domain check before rule arrays become unreadable.
## Three tools for "it depends" On a profile form where `account_type` is `personal` or `business`, the `company_name` and `vat_number` fields only make sense for business accounts. Laravel offers three different answers. ### required_if — "mandatory in this case" `'company_name' => ['required_if:account_type,business', 'nullable', 'string', 'max:100']` - If `account_type` is `business`, `company_name` must be present and non-empty. - Otherwise it may be missing — but if it **is** sent, the other rules still run and the value is still returned by `validated()`. - Related implicit rules: `required_unless`, `required_with`, `required_without`, `required_if_accepted`. ### exclude_if — "pretend it was never sent" `'vat_number' => ['exclude_if:account_type,personal', 'required', 'string', 'size:11']` - If `account_type` is `personal`, the field is **removed** from the data under validation: no rule runs for it and it does not appear in `validated()`. - Otherwise the remaining rules apply normally — here, it becomes required. - This is the right tool when stale values must not be persisted, for example a VAT number left in a hidden input after the user switched to a personal account. **Order matters.** Rules run in the order written. If `required` came before `exclude_if`, a personal account without a VAT number would fail `required` before the exclusion is evaluated. Put `exclude_if` (and `exclude_unless`, `exclude_with`, `exclude_without`) first. ### Rule::when — "use a different rule set" `Rule::when($condition, $rules, $defaultRules = [])` returns a `ConditionalRules` object: - `$condition` is a boolean, or a closure that receives the input wrapped in `Illuminate\Support\Fluent` and returns a boolean. - When true, `$rules` apply; otherwise `$defaultRules` (empty by default). - `Rule::unless()` is the inverse. Use it when the condition is not "field X equals value Y" — for example depending on the signed-in user, a feature flag, or a computed comparison. ## Closure versions of the string rules - `Rule::requiredIf(fn () => $request->user()->is_verified_seller)` — the closure form of `required_if`. - `Rule::excludeIf(fn () => ! $request->user()->can_invoice)` — the closure form of `exclude_if`. - `Rule::prohibitedIf(...)` — the value must be absent or empty when the condition holds. ## Comparison | Tool | Condition source | Field's other rules when condition is false | In `validated()`? | |---|---|---|---| | `required_if:x,v` | another field equals a value | still run if the field is sent | yes, if sent | | `exclude_if:x,v` | another field equals a value | run normally (exclusion applies when true) | no, when excluded | | `Rule::when(...)` | boolean or closure | the default rule set applies | yes, if sent | | `Rule::requiredIf(fn)` | closure | still run if sent | yes, if sent | | `Rule::excludeIf(fn)` | closure or boolean | run normally | no, when excluded | ## Comparing booleans in string rules HTML forms send checkboxes as strings (`"1"`, `"on"`) or not at all, while JSON sends `true`/`false`. The string parameters `true` and `false` in rules like `required_if` are converted to booleans before comparing when the other field has the `boolean` rule **or** its submitted value is already a boolean. So `required_if:has_company,true` matches JSON `true` either way, but a form checkbox posting `"on"` only matches when `has_company` also carries the `boolean` rule; otherwise the string `"on"` is compared with the string `"true"` and never matches. ## Choosing - Mandatory in some cases, harmless otherwise → `required_if`. - Must be dropped when irrelevant → `exclude_if`, placed first. - Condition not expressible as "field equals value" → `Rule::when`, `Rule::requiredIf` or `Rule::excludeIf`. - Rules that depend on each element inside an array → `Rule::forEach`. ## A worked sequence for the profile form For a personal account posting `{"account_type": "personal", "company_name": "", "vat_number": "X"}` with the example rules: 1. `account_type` passes `required` and `in`. 2. `company_name` arrives as `null` (empty string converted by middleware); `required_if` does not apply, `nullable` skips the rest, and `validated()` contains `company_name => null`. 3. `vat_number` hits `exclude_if` first, is removed from the data, and never appears in `validated()` — the stale `X` is not saved. 4. `website` gets the default rule set `['nullable', 'url']` because the `Rule::when` condition is false.
- Why does ['required', 'exclude_if:account_type,personal'] still fail for personal accounts?Rules run in the order listed. `required` runs first and fails on the missing value, and a failed implicit rule stops that field's remaining rules, so `exclude_if` never gets to exclude it. Put exclusion rules first.
- What does the closure passed to Rule::when receive?When the condition is a closure, `ConditionalRules` calls it with the data under validation wrapped in an `Illuminate\Support\Fluent`, so you can read `$input->account_type`. You may also pass a plain boolean computed beforehand.
saying these in an interview costs you the question
- required_if also removes the field from validated() when the condition is false
- exclude_if can go anywhere in the rule list
- Rule::when needs a closure; a boolean is not allowed
- exclude_if only skips rules but keeps the value in validated()