skip to content

In Laravel validation, how do required_if, exclude_if and Rule::when differ when a field depends on another field's value?

level: middleimportance: should knowfreq 42%

answer

  1. require it, drop it, or swap rules
  2. required_if:account_type,business
  3. exclude_if removes it from validated()
  4. put exclude_if first; rules run in order
  5. Rule::when(cond, rules, default)

basics

~20 s

required_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
<?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

for a junior

Recognise required_if for fields that are mandatory only in some cases.

for a middle

Contrast required_if with exclude_if, including what reaches validated(), and write Rule::when with a default rule set.

for a senior

Place exclusion rules first, handle boolean comparisons from forms versus JSON, and prevent stale hidden values being stored.

for a principal

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()