Reject Unexpected Array Keys with Laravel Validation (Laravel 13.24)
Laravel Tips & Tricks #Laravel #Validation #Laravel 13 #Form Request #API

Reject Unexpected Array Keys with Laravel Validation (Laravel 13.24)

3 min read Mohamed Said Mohamed Said

The Problem with Silent Filter Failures

Endpoints that accept a bag of options have a subtle failure mode: a client sends ?filter[stat us]=draft with a typo, your code reads $filters['status'], finds nothing, and returns the full unfiltered list. No error is raised, the response looks correct, and the bug surfaces later as an intermittent mystery.

Laravel 13.24 ships the array_keys validation rule to close this gap. It lets you declare exactly which keys an array may contain and returns a failure message that names what went wrong.

Basic Usage

Both the fluent builder and the string form are supported:

use Illuminate\Validation\Rule;

$request->validate([
    'filter' => Rule::arrayKeys(['status', 'author', 'tag']),
]);

// Equivalent string form
$request->validate([
    'filter' => 'array_keys:status,author,tag',
]);

Given ['status' => 'draft', 'stat us' => 'draft'], validation fails with:

The filter field must only contain the following keys: status, author, tag.

The keys are permitted, not required. To enforce that specific keys must also be present, compose the rule with required_array_keys:

'coordinates' => [
    'required_array_keys:lat,lng',
    Rule::arrayKeys(['lat', 'lng']),
],

Why Not array:key_1,key_2?

Rule::array() has accepted a key list for a while, but it conflates two concerns — type checking and key checking — into one message:

| Rule | Message on unexpected key | |---|---| | array:status,author | The filter field must be an array. | | array_keys:status,author | The filter field must only contain the following keys: status, author. |

The first message is misleading when the value is an array. The new rule separates the concerns and reports them independently in $validator->failed() as Array and ArrayKeys.

Custom Messages with :unexpected

The rule ships two placeholders: :values (the allowed keys) and :unexpected (the keys that caused the failure). The :unexpected placeholder is especially useful in API responses:

$request->validate(
    ['filter' => Rule::arrayKeys(['status', 'author', 'tag'])],
    ['filter.array_keys' => 'The :attribute field may not contain :unexpected.'],
);
// The filter field may not contain colour, sort.

Real-World Example: Filtered Index Endpoint

class IndexPostRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'filter' => ['sometimes', 'array', Rule::arrayKeys(['status', 'author', 'tag'])],
            'filter.status' => ['sometimes', Rule::enum(PostStatus::class)],
            'filter.author' => ['sometimes', 'integer', 'exists:users,id'],
            'filter.tag'    => ['sometimes', 'string', 'max:50'],
            'sort' => ['sometimes', 'string', Rule::in(['title', '-title', 'published_at', '-published_at'])],
        ];
    }

    public function messages(): array
    {
        return [
            'filter.array_keys' => 'Unknown filter: :unexpected. Allowed filters are :values.',
        ];
    }
}

Anything that reaches the controller is a key you explicitly named, so defensive isset checks become unnecessary.

Validating a JSON Column on Writes

The rule is equally useful when persisting a settings or preferences column:

'preferences' => ['sometimes', 'array', Rule::arrayKeys(['theme', 'timezone', 'digest_frequency'])],
'preferences.theme'            => ['sometimes', Rule::in(['light', 'dark', 'system'])],
'preferences.timezone'         => ['sometimes', 'timezone'],
'preferences.digest_frequency' => ['sometimes', Rule::in(['daily', 'weekly', 'never'])],

A renamed frontend field now fails loudly during deployment instead of silently writing a stale key into every row.

Key Behaviours to Know

  • A non-array value fails the rule. Pair with array so the type failure gets its own message.
  • At least one key is required. Passing no keys throws an InvalidArgumentException at runtime. Use prohibited if you want to block the field entirely.
  • Accepts any Arrayable. Collections and backed enums both work: Rule::arrayKeys(FilterKey::cases()).
  • Variadic form is supported. Rule::arrayKeys('status', 'author') is equivalent to passing an array.

The rule was contributed by @nebarg in #60918.


Source: Reject Unexpected Array Keys with Laravel Validation — Laravel News

Found this useful?

Frequently Asked Questions

3 questions
Q01 What is the difference between `array:key_1,key_2` and `array_keys:key_1,key_2` in Laravel validation?
Both reject unexpected keys, but `array` reports a single ambiguous message ('must be an array') even when the value is already an array. `array_keys` reports a dedicated message that names the allowed keys, and the two rules fail independently in `$validator->failed()` so you can handle each case separately.
Q02 Does the `array_keys` rule require all listed keys to be present?
No. It only constrains which keys *may* appear; it does not require any of them. To also enforce presence, combine it with `required_array_keys`: `['required_array_keys:lat,lng', Rule::arrayKeys(['lat', 'lng'])]`.
Q03 How can I show the client exactly which unexpected key failed validation?
Use the `:unexpected` placeholder in a custom message: `'filter.array_keys' => 'The :attribute field may not contain :unexpected.'`. This tells the client the exact key name rather than just the list of allowed keys.

Continue reading

More Articles

View all