passbolt/passbolt_api · error · BadRequestException
Invalid filter.
Error message
Invalid filter. {inner message} What it means
Thrown by QueryStringComponent::validateQueryItems when validating the 'filter' query string key. The component delegates to validateFilters(); if any filter name or value is rejected, the resulting CakeException is caught and re-thrown as a Cake\Http\Exception\BadRequestException prefixed with 'Invalid filter.' plus the inner reason. It signals the client sent an HTTP query parameter that does not conform to the passbolt filter whitelist or value format rules.
Solutions
- Read the inner message after 'Invalid filter.' — it names the exact filter or value problem.
- Correct the filter name to one of the supported keys: search, from, created-before, created-after, has-access, has-id, has-managers, has-users, has-groups, has-parent, is-shared-with-group, modified-after, is-active, is-admin, is-favorite, is-owned-by-me, is-shared-with-me, is-shared, is-success, is-deleted, expired, deleted, has-tag, frequency, metadata_key_type.
- Fix the filter value format: UUIDs for user/resource/group filters, ISO datetimes for date filters, true/false for boolean filters, user_key|shared_key for metadata_key_type.
- If it is a custom filter, register a validator callable in the controller's QueryStringComponent $filterValidators option.
- Remove the filter entirely if it is not needed — unknown filters are rejected, not ignored.
Example fix
// before GET /resources.json?filter[is_deleted]=yes&filter[foo]=1 // after GET /resources.json?filter[is-deleted]=true
Defensive patterns
Strategy: validation
Validate before calling
$allowedFilters = ['search','from','created-before','created-after','has-access','has-id','has-managers','has-users','has-groups','has-parent','is-shared-with-group','modified-after','is-active','is-admin','is-favorite','is-owned-by-me','is-shared-with-me','is-shared','is-success','is-deleted','expired','deleted','has-tag','frequency','metadata_key_type'];
foreach ((array)($query['filter'] ?? []) as $name => $value) {
if (!in_array($name, $allowedFilters, true)) {
throw new InvalidArgumentException("Unsupported filter: $name");
}
}
// additionally: date filters must be strtotime()-parseable, boolean filters in {true,false,0,1},
// has-users/has-groups/has-access/has-id values must be UUIDs, metadata_key_type in {user_key,shared_key} Type guard
function isUuidList(mixed $value): bool {
$list = is_array($value) ? $value : [$value];
return $list !== [] && count(array_filter($list, fn($v) => is_string($v) && preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $v))) === count($list);
} Try / catch
try {
$result = $api->get('/resources.json', ['query' => ['filter' => $filters]]);
} catch (BadRequestException $e) {
if (str_starts_with($e->getMessage(), 'Invalid filter.')) {
$logger->warning('Rejected filters', ['filters' => $filters, 'reason' => $e->getMessage()]);
$result = $api->get('/resources.json'); // retry without filters
} else {
throw $e;
}
} Prevention
- Build query strings from a whitelist map of filter names, never from raw user input keys
- Use kebab-case filter names exactly as documented (is-deleted, not is_deleted)
- Validate UUIDs and dates client-side before sending them as filter values
- Pin your client to the server's API version so filter sets match
- Catch and log 'Invalid filter.' responses in dev to catch typos early
When it happens
Trigger: GET requests with ?filter[...] where a filter name is unknown (or lacks a registered custom validator), a date filter (from/created-before/created-after) is not parseable, a boolean filter (is-active, is-deleted, etc.) is not 'true'/'false'/0/1, has-users/has-groups receive non-UUID values, or metadata_key_type is not 'user_key'/'shared_key'.
Common situations: Client SDKs constructing filters from user input; typos in filter names (e.g. filter[is_deleted] instead of is-deleted); sending snake_case instead of kebab-case; passing unvalidated UUIDs or raw dates from a UI; plugins that forgot to register a filter validator via getFilterValidators.
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 contain.
- Invalid order.
- Filter is not valid.
- No validation rule for filter
- " " is not a valid contain value.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/de97523aa06dd548.
Report an issue: GitHub.
Appendix: source
Thrown at src/Controller/Component/QueryStringComponent.php:244
/**
* Validate query items
*
* @param array $query items to validate
* @param array $allowedQueryItems whitelisted items
* @param array<callable> $filterValidators Filters validator callable
* @return bool true if validate
* @throws \Cake\Http\Exception\BadRequestException if a validation error occurs
*/
public static function validateQueryItems(array $query, array $allowedQueryItems, array $filterValidators): bool
{
foreach ($query as $key => $parameters) {
switch ($key) {
case 'filter':
try {
self::validateFilters($parameters, $filterValidators);
} catch (CakeException $e) {
throw new BadRequestException(__('Invalid filter.') . ' ' . $e->getMessage());
}
break;
case 'order':
try {
self::validateOrders($parameters, $allowedQueryItems);
} catch (CakeException $e) {
throw new BadRequestException(__('Invalid order.') . ' ' . $e->getMessage());
}
break;
case 'contain':
try {
self::validateContain($parameters);
} catch (CakeException $e) {
throw new BadRequestException(__('Invalid contain.') . ' ' . $e->getMessage());
}
break;
}
}View on GitHub (pinned to 31c1bbc10f)