In Laravel, where can you override a validation error message or the :attribute name, and in what order does the validator look for them?
answer
- inline beats the language file
- 'field.rule' before bare 'rule'
- validation.custom.field.rule
- size rules: per-type lines
- validation.attributes renames :attribute
basics
~20 sPass inline messages and attribute names (a form request's messages() and attributes(), or the extra validator arguments) or put them in lang/{locale}/validation.php under custom and attributes. Inline entries win, then custom, then the rule's default line.
solid answer
~40 sFor each failed rule the validator first checks the inline messages: the key `field.rule` (say `household_income.required`), then a bare `rule` key that applies to every field. Next it asks the translator for `validation.custom.{field}.{rule}` in the current locale's `validation.php`; for size rules such as `min` and `max` it then picks the per-type line (`validation.min.string`, `.numeric`, `.array`, `.file`), and otherwise the rule's default `validation.{rule}` line. The `:attribute` placeholder is resolved separately: inline `attributes()` first, then `validation.attributes`, and for a plain field the name snake-cased with underscores turned into spaces. Inline strings bypass the translator, so a bilingual form should keep its wording in `lang/{locale}/validation.php` or wrap `messages()` entries in `__()`.
code
php · 28 lines<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class BenefitApplicationRequest extends FormRequest
{
public function rules(): array
{
return [
'household_income' => ['required', 'integer', 'min:0'],
'national_id' => ['required', 'string', 'size:9'],
];
}
public function messages(): array
{
return [
'household_income.required' => __('Tell us your total household income.'),
];
}
public function attributes(): array
{
return ['national_id' => __('national ID number')];
}
}go deeper
Recall that messages() and attributes() on a form request, or the validator's extra arguments, change the wording, and that :attribute is the field's display name.
Walk the lookup order: inline field.rule, inline rule, validation.custom, the size-rule type lines, then validation.{rule}; and the separate chain for :attribute.
Explain why a translated form still shows English text (inline literals beat custom lines) and how to organise messages so every locale is covered without code changes.
Decide where user-facing validation copy lives, code or language files, so content editors and translators can own it without touching form requests.
## Why a lookup chain exists Laravel ships a default English line for every built-in rule, such as `'required' => 'The :attribute field is required.'`. Those defaults live inside the framework (`Illuminate/Translation/lang/en/validation.php`), so validation produces readable messages even though the application skeleton has no `lang` directory. A real product, say a **government benefits application** offered in English and Spanish, needs friendlier wording: "Tell us your total household income" instead of "The household income field is required". Laravel lets you override messages at several levels and resolves them in a fixed order. ## The message lookup order For a failed rule on a field, `FormatsMessages::getMessage()` tries, in order: 1. **Inline, field and rule**: the key `household_income.required` in the messages array passed to the validator. With a form request that array is the return value of `messages()`; with a manual validator it is the third argument of `Validator::make()` or the second of `$request->validate()`. 2. **Inline, rule only**: a bare `required` key, which overrides that rule for every field. (A key equal to the field name holding an array of rule messages is also accepted.) 3. **Language file, custom**: `validation.custom.household_income.required` from `lang/{locale}/validation.php`. Wildcard field keys work here too. 4. **Size rules**: for `size`, `between`, `min`, `max`, `gt`, `lt`, `gte` and `lte`, the validator decides the value's type (numeric when a `numeric`, `integer` or `decimal` rule is present, array, file, otherwise string) and reads `validation.min.numeric`, `validation.min.string` and so on. 5. **Language file, default**: `validation.required`. 6. **Last resort**: the literal key, such as `validation.my_rule`, which is what you see when a custom rule name has no message anywhere. The rule name is snake-cased for lookup, so `requiredIf` becomes `required_if`. ## The chain in action Take `household_income` with the rules `required`, `integer` and `min:0`, and an applicant who types `-50`. The `required` and `integer` checks pass; `min` fails. No inline `household_income.min` or bare `min` entry exists, and the language file has no `custom.household_income.min`, so step 4 applies: the `integer` rule marks the value as numeric, the validator reads `validation.min.numeric` ("The :attribute field must be at least :min."), and the placeholders turn it into "The household income field must be at least 0." Adding one inline `household_income.min` entry would short-circuit the whole chain at step 1. ## Naming the field: the `:attribute` placeholder The field's display name is resolved independently of the message text: | Source | Example entry | Result in the default line | |---|---|---| | Inline `attributes()` / 4th validator argument | `'national_id' => 'national ID number'` | The national ID number field is required. | | `validation.attributes` in the language file | `'national_id' => 'número de identificación'` | El campo número de identificación es obligatorio. | | Neither (plain field) | none | The national id field is required. | For a plain field the fallback is `Str::snake()` followed by replacing underscores with spaces, so `householdIncome` also becomes "household income". The capitalised variants `:Attribute` and `:ATTRIBUTE` exist for messages that start with the field name. Attribute names change **only the text**: the errors stay keyed by the real field name. ## Other placeholders worth knowing - `:input` inserts the submitted value when it is scalar, useful for "`:input` is not a valid postcode". - `:other` and `:value` appear in rules such as `required_if`; the language file's `values` array can map a raw value (`cc`) to a label (`credit card`). - Rule parameters such as `:min`, `:max` and `:date` are filled in by per-rule replacers. ## Translating a benefits form The translator reads `validation.php` for the **current locale**, and when a key is missing there it falls back to `fallback_locale` (the skeleton sets `APP_FALLBACK_LOCALE` to `en`). Setting the locale and publishing the language files are localization topics; what matters here is where the strings sit: - **Language-file strategy**: keep wording in `lang/en/validation.php` and `lang/es/validation.php` under `custom` and `attributes`. Every locale gets its own text with no code change. - **Inline strategy**: `messages()` and `attributes()` are plain PHP arrays. A literal English string there is **not translated**; wrap it in `__()` so it is looked up in the locale's JSON translations. - **Mixing both**: inline entries win. An English string left in `messages()` silently beats the Spanish `custom` entry, the classic reason a translated form shows one stubborn English message. ## Pitfalls - A bare `required` key in `messages()` rewrites that rule for **all** fields, sometimes by accident. - Custom rule names without any message surface as `validation.rule_name` to users. - `:input` echoes user data; print messages with `{{ }}` so it stays escaped. - `attributes()` does not rename the key used by `@error` or in a JSON body.
- If `messages()` contains a bare `'required' => 'This is needed.'` entry, which failures does it affect?Every `required` failure on every field, except where a more specific inline `field.required` key exists, because the validator tries `{field}.{rule}` before `{rule}`. It also beats any `validation.custom` entry in the language file, since inline messages are consulted before the translator.
- Why does the `min` rule have separate messages for strings, numbers, arrays and files?`min` is a size rule, and size means characters, value, item count or kilobytes depending on the data. The validator infers the type (numeric if a `numeric`, `integer` or `decimal` rule is present, then array, file, otherwise string) and reads `validation.min.{type}`. An inline override for a size rule may be an array keyed by those types.
- What does the `:input` placeholder add, and what must you watch for?It substitutes the submitted value when it is scalar, so a message can say "12345 is not a valid postcode". Because that is raw user input, the message must be echoed escaped with `{{ }}`; printing it raw with `{!! !!}` would turn the error message into a reflected XSS vector.
saying these in an interview costs you the question
- Language-file custom messages override the ones returned by a form request's messages().
- Strings returned from messages() are translated to the current locale automatically.
- Without publishing the lang directory Laravel shows no validation messages at all.
- attributes() renames the key under which the field's errors are stored.
- The :attribute placeholder always prints the raw input name, underscores included.