passbolt/passbolt_api · error · CakeException

No validation rule for filter

Error message

No validation rule for filter {0}. Please create one.

What it means

Raised inside QueryStringComponent::validateFilters() (surfaced to clients as 'Invalid filter. No validation rule for filter {name}.') when a filter name is not one of the hard-coded cases AND no custom validator callable was registered for it in $filterValidators. The component intentionally fails closed: unknown filters must be explicitly whitelisted with a validation rule before they can be used.

Solutions

  1. Check the filter name for typos against the built-in list in validateFilters().
  2. If it is a legitimate new filter, register a validator: pass ['your-filter' => fn($values) => (bool)$values] in the QueryStringComponent config ($filterValidators) of the controller.
  3. If it comes from a plugin, update the plugin or the passbolt version so the filter's validator exists.
  4. If the filter is not needed, remove it from the request.
  5. Confirm client and server API versions match so the client only sends supported filters.

Example fix

// before (controller)
$this->loadComponent('QueryString', ['filterValidators' => []]);
// after
$this->loadComponent('QueryString', ['filterValidators' => [
    'is-expiring' => function ($values) { return is_scalar($values); },
]]);
Defensive patterns

Strategy: validation

Validate before calling

// Server-side guard before relying on a custom filter
$builtin = ['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'];
$registered = array_keys($filterValidators); // passed to QueryStringComponent
if (!in_array($myFilterName, $builtin, true) && !in_array($myFilterName, $registered, true)) {
    throw new LogicException("Filter '$myFilterName' has no validator; register it in filterValidators");
}

Type guard

function filterHasValidator(string $name, array $filterValidators, array $builtin): bool {
    return in_array($name, $builtin, true) || array_key_exists($name, $filterValidators);
}

Try / catch

try {
    $result = $api->get('/items.json', ['query' => ['filter' => $filters]]);
} catch (BadRequestException $e) {
    if (str_contains($e->getMessage(), 'No validation rule for filter')) {
        $logger->error('Server does not support filter', ['filter' => array_keys($filters)]);
        throw new UnsupportedFilterException($e->getMessage(), previous: $e);
    }
    throw $e;
}

Prevention

When it happens

Trigger: A request uses ?filter[some-custom-key]=... where some-custom-key is neither in the switch (search, from, created-*, has-*, is-*, modified-after, expired, deleted, has-tag, frequency, metadata_key_type) nor present in the $filterValidators array passed by the controller/plugin.

Common situations: A plugin introduced a new filter but forgot to register its validator; a typo in a filter name so it falls through to the default branch; a client sending filters from a different (newer/older) API version; developers testing a new filter endpoint before writing the validator.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — 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/3cb7538fc5c21d7e. Report an issue: GitHub.

Appendix: source

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

                    case 'is-deleted':
                    case 'expired':
                    case 'deleted':
                        self::validateFilterBoolean($values, $filterName);
                        break;
                    case 'has-tag':
                        self::validateFilterString($values, $filterName);
                        break;
                    case 'frequency':
                        self::validateFilterInteger($values, $filterName);
                        break;
                    case 'metadata_key_type':
                        self::validateFilterInList($values, $filterName, ['user_key', 'shared_key']);
                        break;
                    default:
                        // Check if custom filter validators were defined for this filter
                        if (!isset($filterValidators[$filterName])) {
                            $msg = __('No validation rule for filter {0}. Please create one.', $filterName);
                            throw new CakeException($msg);
                        }

                        if (!call_user_func($filterValidators[$filterName], $values)) {
                            throw new CakeException(__('Filter {0} is not valid.', $filterName));
                        }

                        break;
                }
            }
        }

        return true;
    }

    /**
     * Check if the filter is a valid string
     *
     * @param mixed $value to check

View on GitHub (pinned to 31c1bbc10f)