A form request is a class whose rules run before the action. php artisan make:request StoreProductRequest writes one whose authorize() returns false, so every request gets 403 "This action is unauthorized." until you change or delete it. The The Available Validation Rules rules went into its rules(), and the update request extends it:
class UpdateProductRequest extends StoreProductRequest // parent has #[FailOnUnknownFields]
{
public function rules(): array
{
return [
...parent::rules(),
'sku' => ['required', 'string', 'max:20',
Rule::unique('products')->ignore($this->route('product'))],
];
}
}The actions type-hint the classes: store(StoreProductRequest $request) saves $request->safe()->merge(['slug' => Str::slug($request->name)])->all() and update() saves $request->validated(). Without ignore(), a book resaved with its own SKU would be "taken". #[FailOnUnknownFields], added in Laravel 13.4 2,157 (April 2026) and inherited here from the parent, rejects keys that have no rule instead of silently dropping them:
$ A='-u admin@example.com:secret123'; J='Accept: application/json'
$ errs() { jq -r '.errors | to_entries[] | "\(.key): \(.value | join(" "))"'; }
$ P='name=The Pragmatic Programmer&category_id=1&format=paperback&price=49.95'
$ curl -s -o /dev/null -w '%{http_code}\n' $A -H "$H" -X PUT -d "$P&sku=BK-1001" $B/products/1
302
$ curl -s $A -H "$H" -H "$J" -X PUT -d "$P&sku=BK-1002&is_featured=1" $B/products/1 | errs
sku: The sku has already been taken.
is_featured: The is featured field is prohibited.FormRequest::failOnUnknownFields() applies it globally and #[FailOnUnknownFields(false)] exempts a class. Its siblings are #[StopOnFirstFailure], #[ErrorBag] and #[RedirectTo].