passbolt/passbolt_api · error · BadRequestException

Invalid query string. The contain parameter should be an…

Error message

Invalid query string. The contain parameter should be an array.

What it means

QueryStringComponent::normalizeQueryItems() requires the `contain` query parameter to be an array. If `contain` is present but not an array it throws BadRequestException('Invalid query string. The contain parameter should be an array.') producing a 400. Contain clauses tell the API which associations to embed, and each value is normalized to a boolean.

Solutions

  1. Use array syntax: `?contain[]=profile` (or `?contain%5B%5D=profile`) instead of a bare scalar value.
  2. For multiple associations repeat the parameter: `?contain[]=profile&contain[]=groups-users`.
  3. Fix the client query builder so contain keys are emitted as arrays, and verify bracket URL-encoding.
  4. If you don't need associations, omit the contain parameter entirely.

Example fix

// before — scalar contain, rejected
GET /users.json?contain=profile
// after — array syntax
GET /users.json?contain[]=profile
// multiple associations:
GET /users.json?contain[]=profile&contain[]=gpgkey
Defensive patterns

Strategy: type-guard

Validate before calling

// ensure contain serializes as array query params
function buildContainParams(contains) {
  const p = new URLSearchParams();
  for (const c of contains) p.append('contain[]', c); // emits contain[]=value
  return p;
}

Type guard

const isContainArray = (v) => v === undefined || v === null || Array.isArray(v); // a bare string like 'profile' is rejected server-side

Try / catch

try {
  return await api.get('/users.json', { params });
} catch (e) {
  if (e.response?.status === 400 && /contain parameter should be an array/.test(e.response?.data?.message ?? '')) {
    throw new Error('Send associations as contain[]=name, not contain=name');
  }
  throw e;
}

Prevention

When it happens

Trigger: GET collection/detail endpoints with `?contain=<scalar>` — e.g. `?contain=profile` instead of `?contain[]=profile`, or `?contain=1`; a contain value that URL parsing turns into a string rather than an array element.

Common situations: Clients omitting the `[]` in `contain[]=`; copying single-association examples written as `contain=groups` from other APIs; URL-encoding bugs dropping brackets; templated API wrappers joining contain values with commas into one string.

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 passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/a9ade49cad3fb9ac. Report an issue: GitHub.

Appendix: source

Thrown at src/Controller/Component/QueryStringComponent.php:146

                        if ($parentId === 'false' || $parentId === '0') {
                            $query['filter']['has-parent'][$i] = false;
                        }
                    }
                } elseif ($filterName === 'from') {
                    try {
                        $query['filter']['from'] = new DateTime($query['filter']['from']);
                    } catch (Exception $e) {
                        $query['filter']['from'] = false;
                    }
                } elseif ($filterName === 'frequency') {
                    $query['filter'][$filterName] = self::normalizeInteger($filter);
                }
            }
        }
        // idem with contain clauses
        if (isset($query['contain'])) {
            if (!is_array($query['contain'])) {
                throw new BadRequestException(__('Invalid query string. The contain parameter should be an array.'));
            }
            foreach ($query['contain'] as $containName => $contain) {
                $query['contain'][$containName] = self::normalizeBoolean($contain);
            }
        }

        return $query;
    }

    /**
     * Extract array string items
     *
     * @param array $query original query string items
     * @param array $allowedQueryItems whitelist
     * @return array $query the sanitized query
     */
    public static function unsetUnwantedQueryItems(array $query, array $allowedQueryItems): array
    {

View on GitHub (pinned to 31c1bbc10f)