In Laravel validation, how do you validate every element of a submitted array and keep unexpected nested keys out of the validated data?
answer
- dot notation and the * wildcard
- links.*.url, tags.* with distinct
- array:label,url lists allowed keys
- child rules drop unvalidated nested keys
- bare 'array' returns the whole array
basics
~20 sGive the parent 'array' (optionally with allowed keys, array:label,url) and write rules per element with the * wildcard, such as links.*.url. When child rules exist, validated() returns only validated nested keys; a bare array rule passes the whole array through.
solid answer
~40 sLaravel validates nested input with dot notation: `'links' => ['array', 'max:5']`, then `'links.*.label' => ['required', 'string']` and `'links.*.url' => ['required', 'url']`; the `*` expands to every index. `distinct` rejects duplicate values across elements. Listing keys in the parent rule, `array:label,url`, makes any extra key such as `is_verified` a validation error. By default the validator factory excludes unvalidated array keys: when a parent array has child rules, `validated()` returns only the validated children, not the whole array. A parent with only `'array'` and no child rules is returned as-is, with every nested key the client sent — the case that lets unexpected data reach `update()`.
code
php · 10 lines<?php
$rules = [
'links' => ['present', 'list', 'max:5'],
'links.*' => ['array:label,url'],
'links.*.label' => ['required', 'string', 'max:30'],
'links.*.url' => ['required', 'url', 'distinct'],
'tags' => ['nullable', 'array', 'max:10'],
'tags.*' => ['string', 'alpha_dash', 'max:20', 'distinct:ignore_case'],
];go deeper
Know dot notation and that links.*.url validates the url of every element.
Use array with allowed keys, list, distinct and size rules on arrays, and read error keys like links.2.url.
Explain what validated() returns for arrays with and without child rules, and close the gap a bare array rule leaves.
Set API conventions for nested payloads, rejecting unknown keys versus dropping them, and apply them consistently.
## Dot notation and wildcards Nested input — `links[0][url]` from a form, or a JSON array — is addressed with dots: - `'links'` — the whole list. - `'links.0.url'` — one element's `url`. - `'links.*.url'` — the `url` of **every** element; the validator expands `*` against the actual input. A profile form with up to five social links might use: - `'links' => ['array', 'max:5']` - `'links.*' => ['array:label,url']` - `'links.*.label' => ['required', 'string', 'max:30']` - `'links.*.url' => ['required', 'url', 'distinct']` ## Rules that help with arrays | Rule | Checks | |---|---| | `array` | the value is a PHP array | | `array:label,url` | the value is an array **and** every key is in the list; extra keys fail | | `list` | the array's keys are `0..n-1` in order, i.e. a JSON list, not an object | | `distinct` | on a `*` field, no two elements share the value (`distinct:ignore_case`, `distinct:strict`) | | `min` / `max` / `size` on an array | number of elements | | `required_array_keys:label,url` | the listed keys must all be present | ## What reaches validated() This is where nested validation is easy to get wrong. Laravel's validator factory has **exclude unvalidated array keys** switched on by default. The effect on `validated()`: 1. For each rule key, the value is copied into the result. 2. If a key has the `array` (or `list`) rule **and** there are child rules under it (`links.*...`), the parent's whole array is **not** copied; only the children that have rules are. 3. If a key has `array` with **no** child rules, its whole value — every nested key the client sent — is copied. So: - `'links' => ['array']` alone → `validated()['links']` contains whatever the client posted, including keys like `is_verified`. - `'links' => ['array']` plus `'links.*.url'` and `'links.*.label'` → only `url` and `label` for each element. - `'links.*' => ['array:label,url']` → an extra key is a **validation error**, not just dropped. The docs advise always listing allowed keys on an `array` rule. The two mechanisms differ: exclusion silently drops extras from `validated()`, while `array:keys` rejects the request. `Validator::includeUnvalidatedArrayKeys()` switches the default off globally, which is rarely what you want. ## Per-element conditions When a rule depends on the element itself, `Rule::forEach(fn ($value, $attribute) => [...])` returns rules per expanded attribute, for example an `exists` check scoped by each item's type. Error messages for array fields are keyed by the expanded path (`links.2.url`), which is how the view or JSON consumer knows which row failed. ## Common mistakes - Validating `links.*.url` but not `links` itself: a client sending a string for `links` makes the wildcard match nothing and passes silently. Add `'links' => ['array']` (and `required` or `present` as the contract demands). - Relying on `$fillable` alone: a JSON column cast to an array accepts any nested structure; nested keys must be constrained by validation. - Forgetting `list` on APIs where order and indices matter; an object like `{"a": {...}}` otherwise satisfies `array`. - Using `distinct` on the parent instead of on the `*` field. ## Worked example: profile links A client sends: ```json {"links": [{"label": "Blog", "url": "https://example.com", "is_verified": true}]} ``` With `'links' => ['array', 'max:5']`, `'links.*.label'` and `'links.*.url'` rules, `validated()` returns the label and url only. Add `'links.*' => ['array:label,url']` and the same request fails with an error on `links.0` instead — the right choice when the client should be told its payload is wrong. ## Checklist for nested input - A rule on the parent (`array` or `list`, plus `required`/`present`/`nullable` as the contract demands). - An allowed-keys list on each object-shaped level (`array:label,url`). - Wildcard rules for every leaf value you intend to store. - `distinct` where duplicates make no sense, with `ignore_case` for user-typed text. - A size limit (`max:5`) so a client cannot post thousands of elements.
- What is the difference between array:label,url and simply having rules for links.*.label and links.*.url?`array:label,url` rejects any element containing another key, so the request fails with an error. Child rules without the key list let the request pass and, by default, drop unvalidated nested keys from `validated()`. Use the key list when the client should be told its payload is wrong.
- A request sends links as a plain string and only links.*.url has rules. What happens?The wildcard expands against actual array elements; a string has none, so no `links.*.url` rule runs and the request can pass. Always add a rule on the parent, such as `array` or `list`, plus `required` or `present` if the contract needs it.
saying these in an interview costs you the question
- validated() always returns only the nested keys that have rules, even with a bare array rule
- The * wildcard also validates the parent is an array
- distinct belongs on the parent array field
- array:label,url silently drops extra keys instead of failing