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

  1. Read the inner message after 'Invalid filter.' — it names the exact filter or value problem.
  2. 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.
  3. 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.
  4. If it is a custom filter, register a validator callable in the controller's QueryStringComponent $filterValidators option.
  5. 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

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


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)