The problem
The order service exposes its orders through
the API Platform resource OrderResource, linked
to the Order entity. We add a preparation
priority, an integer, to Order and
OrderResource. API consumers want to filter by
threshold: /orders?priority[gte]=3.
ComparisonFilter, introduced in 4.3, covers
this need. It wraps an equality filter and adds the
gt, gte, lt,
lte and ne operators to it.
Version 5.0 adds between.
// src/ApiResource/OrderResource.php (GetCollection operation)
use ApiPlatform\Doctrine\Orm\Filter\ComparisonFilter;
use ApiPlatform\Doctrine\Orm\Filter\ExactFilter;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\QueryParameter;
new GetCollection(
parameters: [
'priority' => new QueryParameter(
filter: new ComparisonFilter(new ExactFilter()),
property: 'priority',
),
],
),
Filtering works, the documentation does not. Swagger UI and
Scalar list priority[gt],
priority[gte] and so on, each typed
object with the five operators as properties.
Swagger UI's "Execute" button then sends
gt=...>e=..., without the
priority prefix. The filter receives nothing
and the response contains every order.
The cause: the filter describes itself twice.
getSchema() returns an object with one property
per operator, set on priority.
getOpenApiParameters() returns one OpenAPI
parameter per operator, without a schema.
While building the document,
OpenApiFactory merges each operator with that
parent parameter. Lacking a schema of its own, each one
inherits the object, with style: form and
explode: true. The OpenAPI specification
serializes such an object as key=value pairs,
without the parameter name.
We reproduced it on 4.4.2, and on 5.0.1 with six operators
(between included). The code at fault dates
back to 4.3.0.
The solution
Upgrade. Versions 4.4.3 and 5.0.2, released
on 2 October 2026, fix the filter. Each operator gets a
scalar schema and explode: false: Swagger UI
shows plain text fields.
The type stays string, because
ExactFilter does not describe the property's
type. To document an integer, or on an earlier version (4.3
did not get the fix), we override the documentation.
Override openApi. This
property of QueryParameter accepts a list of
OpenAPI Parameter objects: one per operator,
with a simple schema.
// src/ApiResource/OrderResource.php (GetCollection operation)
use ApiPlatform\OpenApi\Model\Parameter;
'priority' => new QueryParameter(
filter: new ComparisonFilter(new ExactFilter()),
property: 'priority',
openApi: [
new Parameter('priority[gt]', 'query', 'Priority strictly greater than', schema: ['type' => 'integer']),
new Parameter('priority[gte]', 'query', 'Priority greater than or equal to', schema: ['type' => 'integer']),
new Parameter('priority[lt]', 'query', 'Priority strictly less than', schema: ['type' => 'integer']),
new Parameter('priority[lte]', 'query', 'Priority less than or equal to', schema: ['type' => 'integer']),
new Parameter('priority[ne]', 'query', 'Priority not equal to', schema: ['type' => 'integer']),
// 5.0 only: two inclusive bounds, separated by two dots.
new Parameter('priority[between]', 'query', 'Priority between two bounds', schema: ['type' => 'string'], example: '2..4'),
],
),
Each name is complete: priority[gte], not
gte. Swagger UI and Scalar then show integer
fields, and the request goes out as
priority[gte]=3. An operator missing from the
list stays active, but disappears from the documentation.
On 5.0, priority[between]=2..4 returns
priorities 2 to 4, bounds included. A malformed value
(2-4) is ignored without an error: every order
comes back.
The pitfall
Do not use the schema key of
QueryParameter.
It looks like documentation, but it acts at runtime. API
Platform derives validation constraints from it and, with
castToNativeType: true, a type conversion.
Yet the parameter's value is not an integer: it is the array
of operators, for example ['gte' => '3']. On
4.4 as on 5.0:
-
schema: ['type' => 'integer', 'minimum' => 1, 'maximum' => 5]: every request that uses it gets a 422, "This value should be a valid number."; -
schema: ['type' => 'integer']withcastToNativeType: true: 422 as well, "This value should be of type integer."; -
minimumalone rejects nothing andmaximumalone rejects everything: PHP considers an array greater than any number.
On its own,
schema: ['type' => 'integer'] passes and
even fixes the display up to 4.4.2 and 5.0.1. Since the
fixes, the filter's schema wins: the key no longer changes
the documentation.
To validate the values, we use constraints,
applied to the whole array.
constraints: [new Assert\All([new
Assert\Regex('/^\d+$/')])]
rejects priority[gte]=abc with a 422 (widen the
pattern for between). The rule to remember:
openApi describes,
constraints validates.
Sources
-
API Platform,
Filters, OpenAPI and JSON Schema
and
parameter validation: the
openApi,schema,castToNativeTypeandconstraintsproperties. -
API Platform,
Comparison Filter
(5.0): operators and
between. -
API Platform, 4.4.2 code:
ComparisonFilter,
ParameterResourceMetadataCollectionFactory
(
addFilterMetadata), OpenApiFactory (mergeParameter) and ParameterValidationConstraints. - API Platform, issue #8589 and fix #8605, shipped in 4.4.3 and 5.0.2.
-
API Platform,
5.0.2 CHANGELOG:
ComparisonFilterin 4.3.0 (#7760),betweenin 5.0.0 (#8351). -
OpenAPI 3.2,
style examples:
formserialization of an object. - PHP, comparison across types: an array is always greater.