passbolt/passbolt_api · error · BadRequestException
Invalid query string. The filter parameter should be an…
Error message
Invalid query string. The filter parameter should be an array.
What it means
QueryStringComponent::normalizeQueryItems() requires the `filter` query parameter to be an array. If `filter` is present but not an array (and not the literal '[]', which is normalized to []), it throws BadRequestException('Invalid query string. The filter parameter should be an array.') producing a 400.
Solutions
- Send filters using array syntax: `?filter[]=has-managers` or named filters `?filter[is-deleted]=true`.
- To request no filters, either omit the parameter entirely or send the literal `?filter=[]` (explicitly normalized to an empty array).
- Check URL encoding — `[]` must survive encoding (`filter%5B%5D=...`); fix client-side query builders that encode brackets incorrectly.
- If using a JSON body style, stop: this API expects bracket-array query strings, not JSON in query parameters.
Example fix
// before — scalar filter, rejected GET /users.json?filter=has-access // after — array syntax GET /users.json?filter[]=has-access // or multiple: GET /users.json?filter[]=is-admin&filter[]=active
Defensive patterns
Strategy: type-guard
Validate before calling
// ensure filters serialize as array query params
function buildFilterParams(filters) {
const p = new URLSearchParams();
for (const f of filters) p.append('filter[]', f); // emits filter[]=value
return p;
} Type guard
const isFilterArray = (v) => v === undefined || v === null || Array.isArray(v) || (typeof v === 'object' && v !== null); // never a bare string/number
Try / catch
try {
return await api.get('/users.json', { params });
} catch (e) {
if (e.response?.status === 400 && /filter parameter should be an array/.test(e.response?.data?.message ?? '')) {
throw new Error('Send filters as filter[]=value, not filter=value');
}
throw e;
} Prevention
- Always emit `filter[]=` (URL-encoded `filter%5B%5D=`) in query builders, never a bare `filter=`.
- Do not put JSON objects into query string values for filters.
- Add a unit test asserting serialized filter parameters keep bracket syntax after URL encoding.
- When no filters apply, omit the parameter rather than sending a scalar.
When it happens
Trigger: GET collection endpoints with `?filter=<scalar>` — e.g. `?filter=has-managers` (bare string instead of `filter[]=has-managers`), a JSON object instead of array syntax, or a filter supplied with mismatched bracket syntax so PHP parses it as a string.
Common situations: Hand-built query strings missing the `[]` in filter names; clients serializing filters as JSON (`{"...":...}`) instead of PHP/bracket array syntax; URL encoding stripping the brackets; copying examples from other APIs with different filter conventions.
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
- Invalid query string. The contain parameter should be an…
- " " is not a valid group filter.
- " " is not a valid group id for filter .
- " " is not a valid parent filter.
- " " is not a valid parent id for filter .
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/83a2d48289a26469.
Report an issue: GitHub.
Appendix: source
Thrown at src/Controller/Component/QueryStringComponent.php:113
* @param array $query original query string items
* @return array modified query
*/
public static function normalizeQueryItems(array $query): array
{
// order should always be an array even when one value is provided
// this is deprecated, order is now handled by the ApiPaginationComponent
if (isset($query['order']) && !is_array($query['order'])) {
$query['order'] = [$query['order']];
}
// filters with is-* means we are expecting a boolean
// we accept 'TRUE', 'true', '1' as true and the rest is set to false
if (isset($query['filter'])) {
if ($query['filter'] == '[]') {
$query['filter'] = [];
}
if (!is_array($query['filter'])) {
throw new BadRequestException(__('Invalid query string. The filter parameter should be an array.'));
}
foreach ($query['filter'] as $filterName => $filter) {
if (!is_string($filterName)) {
continue;
}
if (in_array($filterName, self::mustBeArrayFilters())) {
// these should always be an array
$query['filter'][$filterName] = $filter = (array)$query['filter'][$filterName];
}
$booleanFilters = ['deleted', 'expired'];
if (substr($filterName, 0, 3) === 'is-' || in_array($filterName, $booleanFilters)) {
$query['filter'][$filterName] = self::normalizeBoolean($filter);
} elseif ($filterName === 'has-parent') {
foreach ($query['filter']['has-parent'] as $i => $parentId) {
if ($parentId === 'false' || $parentId === '0') {
$query['filter']['has-parent'][$i] = false;
}
}View on GitHub (pinned to 31c1bbc10f)