symfony/http-kernel · error · HttpException

Invalid query parameter

Error message

Invalid query parameter "%s".

What it means

Thrown when a #[MapQueryParameter] argument typed as array (non-variadic, no custom filter) receives query values that are not all arrays. The resolver filters the value keeping only elements that are themselves arrays (array_filter with is_array); if any element was dropped and FILTER_NULL_ON_FAILURE was not set, the whole parameter is rejected. In practice this fires when a plain scalar or list of scalars is sent for a parameter declared as array-of-array.

Solutions

  1. Send nested query syntax producing arrays of arrays, e.g. ?filter[from]=1&filter[to]=2 becomes filter as an array of arrays only if each value is itself an array — or restructure the URL
  2. If a flat array is intended, make the argument variadic (#[MapQueryParameter] array ...$ids) or add an explicit filter, e.g. #[MapQueryParameter(filter: FILTER_DEFAULT)] which routes through filter_var instead of this array branch
  3. Pass flags: FILTER_NULL_ON_FAILURE in #[MapQueryParameter] to get null/dropped values instead of a 4xx
  4. Change the controller type to string/int and split manually if only one value is expected

Example fix

// before
public function search(#[MapQueryParameter] array $tags) { ... }
// request: GET /search?tags=php  -> 422 Invalid query parameter "tags"

// after
public function search(#[MapQueryParameter(filter: \FILTER_DEFAULT)] array $tags) { ... }
// request: GET /search?tags[]=php&tags[]=symfony works
Defensive patterns

Strategy: validation

Validate before calling

// send nested arrays only if the argument is plain `array`, else send flat lists with a filter configured
const usp = new URLSearchParams();
['php','symfony'].forEach(t => usp.append('tags[]', t)); // flat list; pair with filter: FILTER_DEFAULT on the server

Type guard

function isArrayOfArrays(v: unknown): v is unknown[][] {
  return Array.isArray(v) && v.every(Array.isArray);
}

Try / catch

try {
  const res = await fetch(url);
  if (res.status === 422 && (await res.text()).includes('Invalid query parameter')) {
    // reshape the query value (scalar -> nested array or flat list) and retry
  }
} catch (e) {}

Prevention

When it happens

Trigger: Controller declares #[MapQueryParameter] array $filter and the client sends ?filter=foo (scalar) or ?filter[]=1&filter[]=2 (array of scalars); only nested-array values like filter[a][b]=1 survive the is_array filter, so anything else triggers the throw unless FILTER_NULL_ON_FAILURE is passed in the attribute flags.

Common situations: Client sends a simple list (?ids[]=1&ids[]=2) while the controller argument is typed array with no variadic and no filter — the developer expected a flat array but Symfony's unfiltered array branch demands arrays-of-arrays; version migrations where behavior around typed array query params changed.

Understand the failure class

Background: "Invalid query parameter" / "Failed to parse value of ...": fixing bad query string parameters across APIs — this error's family across 36 libraries.

Related errors


AI-assisted analysis of symfony/http-kernel@aa3a39d728 (2026-09-13). Data as JSON: /api/errors/ebe16fdcef485c29. Report an issue: GitHub.

Appendix: source

Thrown at Controller/ArgumentResolver/QueryParameterValueResolver.php:64

            if ($argument->isNullable() || $argument->hasDefaultValue()) {
                return [];
            }

            throw HttpException::fromStatusCode($validationFailedCode, \sprintf('Missing query parameter "%s".', $name));
        }

        $value = $request->query->all()[$name];
        $type = $argument->getType();

        if (null === $attribute->filter && 'array' === $type) {
            if (!$argument->isVariadic()) {
                return [(array) $value];
            }

            $filtered = array_values(array_filter((array) $value, \is_array(...)));

            if ($filtered !== $value && !($attribute->flags & \FILTER_NULL_ON_FAILURE)) {
                throw HttpException::fromStatusCode($validationFailedCode, \sprintf('Invalid query parameter "%s".', $name));
            }

            return $filtered;
        }

        $options = [
            'flags' => $attribute->flags | \FILTER_NULL_ON_FAILURE,
            'options' => $attribute->options,
        ];

        if ('array' === $type || $argument->isVariadic()) {
            $value = (array) $value;
            $options['flags'] |= \FILTER_REQUIRE_ARRAY;
        } else {
            $options['flags'] |= \FILTER_REQUIRE_SCALAR;
        }

        $uidType = null;

View on GitHub (pinned to aa3a39d728)