diff --git a/core/filters.md b/core/filters.md
index 167ce981843..8852390df35 100644
--- a/core/filters.md
+++ b/core/filters.md
@@ -1,470 +1,757 @@
-# Filters
+# Parameters and Filters
-API Platform provides a generic system to apply filters and sort criteria on collections.
-Useful filters for Doctrine ORM, Eloquent ORM, MongoDB ODM and ElasticSearch are provided with the library.
+API Platform provides a generic and powerful system to apply filters, sort criteria, and handle other request parameters. This system is primarily managed through **Parameter attributes** (`#[QueryParameter]` and `#[HeaderParameter]`), which allow for detailed and explicit configuration of how an API consumer can interact with a resource.
-You can also create custom filters that fit your specific needs.
-You can also add filtering support to your custom [state providers](state-providers.md) by implementing interfaces provided
-by the library.
+These parameters can be linked to **Filters**, which are classes that contain the logic for applying criteria to your persistence backend (like Doctrine ORM or MongoDB ODM).
-By default, all filters are disabled. They must be enabled explicitly.
-
-When a filter is enabled, it automatically appears in the [OpenAPI](openapi.md) and [GraphQL](graphql.md) documentations.
-It is also automatically documented as a `search` property for JSON-LD responses.
+You can declare parameters on a resource class to apply them to all operations, or on a specific operation for more granular control. When parameters are enabled, they automatically appear in the Hydra, [OpenAPI](openapi.md) and [GraphQL](graphql.md) documentations.

Watch the Filtering & Searching screencast
-For the **specific filters documentation**, please refer to the following pages, depending on your needs:
-- [Doctrine filters documentation](../core/doctrine-filters.md)
-- [Elasticsearch filters documentation](../core/elasticsearch-filters.md)
-- [Laravel filters documentation](../laravel/filters.md)
-
-## Parameters
+For documentation on the specific filter implementations available for your persistence layer, please refer to the following pages:
-You can declare parameters on a Resource or an Operation through the `parameters` property.
+* [Doctrine Filters](../core/doctrine-filters.md)
+* [Elasticsearch Filters](../core/elasticsearch-filters.md)
-```php
-namespace App\ApiResource;
+## Declaring Parameters
-use ApiPlatform\Metadata\GetCollection;
-use ApiPlatform\Metadata\QueryParameter;
-
-// This parameter "page" works only on /books
-#[GetCollection(uriTemplate: '/books', parameters: ['page' => new QueryParameter])]
-// This parameter is available on every operation, key is mandatory
-#[QueryParameter(key: 'q', property: 'freetextQuery')]
-class Book {}
-```
+The recommended way to define parameters is by using Parameter attributes directly on a resource class or on an operation. API Platform provides two main types of Parameter attributes based on their location (matching the OpenAPI `in` configuration):
-Note that `property` is used to document the Hydra view. You can also specify an [OpenAPI Parameter](https://api-platform.com/docs/guides/extend-openapi-documentation/) if needed.
-A Parameter can be linked to a filter, there are two types of filters:
+* `ApiPlatform\Metadata\QueryParameter`: For URL query parameters (e.g., `?name=value`).
+* `ApiPlatform\Metadata\HeaderParameter`: For HTTP headers (e.g., `Custom-Header: value`).
-- metadata filters, most common are serializer filters (PropertyFilter and GroupFilter) that alter the normalization context
-- query filters that alter the results of your database queries (Doctrine, Eloquent, Elasticsearch etc.)
+You can declare a parameter on the resource class to make it available for all its operations:
-### Alter the Operation via a parameter
+```php
+withNormalizationContext(['groups' => $request->query->all('groups')]);
- }
+#[ApiResource]
+#[QueryParameter(key: 'author')]
+class Book
+{
+ // ...
}
```
-Then plug this provider on your parameter:
+Or you can declare it on a specific operation for more targeted use cases:
```php
-namespace App\ApiResource;
+ new HeaderParameter(provider: GroupsParameterProvider::class)])]
-class Book {
- public string $id;
+#[ApiResource(
+ operations: [
+ new GetCollection(
+ parameters: [
+ 'name' => new QueryParameter(description: 'Filter our friends by name'),
+ 'Request-ID' => new HeaderParameter(description: 'A unique request identifier') // keys are case insensitive
+ ]
+ )
+ ]
+)]
+class Friend
+{
+ // ...
}
```
-If you use Symfony, but you don't have autoconfiguration enabled, declare the parameter as a tagged service:
+### Filtering a Single Property
-```yaml
-services:
- ApiPlatform\Tests\Fixtures\TestBundle\Parameter\CustomGroupParameterProvider:
- tags:
- - name: 'api_platform.parameter_provider'
- key: 'ApiPlatform\Tests\Fixtures\TestBundle\Parameter\CustomGroupParameterProvider'
-```
-
-With Laravel the services are automatically tagged.
+Most of the time, a parameter maps directly to a property on your resource. For example, a `?name=Frodo` query parameter would filter for resources where the `name` property is "Frodo". This behavior is often handled by built-in or custom filters that you link to the parameter.
-### Declare a filter with Laravel
+For Hydra, you can map a query parameter to `hydra:freetextQuery` to indicate a general-purpose search query.
-Filters are classes implementing our `ApiPlatform\Laravel\Eloquent\Filter\FilterInterface`, use [our code](https://github.com/api-platform/core/tree/main/src/Laravel/Eloquent/Filter) as examples. Filters are automatically registered and tagged by our `ServiceProvider`.
-
-### Declare a filter with Symfony
+```php
+ new QueryParameter(property: 'hydra:freetextQuery', required: true)
+ ]
+ )
+])]
+class Issue {}
+```
-```yaml
-# config/services.yaml
-services:
- offer.order_filter:
- parent: 'api_platform.doctrine.orm.order_filter'
- arguments:
- $properties: { id: ~, name: ~ }
- $orderParameterName: order
- tags: ['api_platform.filter']
+This will generate the following Hydra `IriTemplateMapping`:
+```json
+{
+ "@context": "http://www.w3.org/ns/hydra/context.jsonld",
+ "@type": "IriTemplate",
+ "template": "http://api.example.com/issues{?q}",
+ "variableRepresentation": "BasicRepresentation",
+ "mapping": [
+ {
+ "@type": "IriTemplateMapping",
+ "variable": "q",
+ "property": "hydra:freetextQuery",
+ "required": true
+ }
+ ]
+}
```
-We can use this filter specifying we want a query parameter with the `:property` placeholder:
+### Filtering Multiple Properties with `:property`
-```php
-namespace App\ApiResource;
+Sometimes you need a generic filter that can operate on multiple properties. You can achieve this by using the `:property` placeholder in the parameter's `key`.
+```php
+ new QueryParameter(filter: 'offer.order_filter'),
- ]
-)
-class Offer {
- public string $id;
- public string $name;
+#[ApiResource(operations: [
+ new GetCollection(
+ parameters: [
+ 'search[:property]' => new QueryParameter(
+ filter: new SearchFilter(properties: ['title' => 'partial', 'description' => 'partial'])
+ )
+ ]
+ )
+])]
+class Book
+{
+ // ...
}
```
-### Header parameters
+This configuration creates a dynamic parameter. API clients can now filter on any of the properties configured in the `SearchFilter` (in this case, `title` and `description`) by using a URL like `/books?search[title]=Ring` or `/books?search[description]=journey`.
-The `HeaderParameter` attribute allows to create a parameter that's using HTTP Headers instead of query parameters:
+When using the `:property` placeholder, API Platform automatically populates the parameter's `extraProperties` with a `_properties` array containing all the available properties for the filter. Your filter can access this information:
```php
-namespace App\ApiResource;
-
-use ApiPlatform\Metadata\HeaderParameter;
-use App\Filter\MyApiKeyFilter;
-
-#[HeaderParameter(key: 'API_KEY', filter: MyApiKeyFilter::class)]
-class Book {
+public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
+{
+ $parameter = $context['parameter'] ?? null;
+ $properties = $parameter?->getExtraProperties()['_properties'] ?? [];
+
+ // $properties contains: ['title' => 'title', 'description' => 'description']
+ // This allows your filter to know which properties are available for filtering
}
```
-When you declare a parameter on top of a class, you need to specify it's key.
+### Restricting Properties with `:property` Placeholders
-### The :property placeholder
+There are two different approaches to property restriction depending on your filter design:
-When used on a Parameter, the `:property` placeholder allows to map automatically a parameter to the readable properties of your resource.
+#### 1. Legacy Filters (SearchFilter, etc.) - Not Recommended
-```php
-namespace App\ApiResource;
+> [!WARNING]
+> Filters that extend `AbstractFilter` with pre-configured properties are considered legacy. They don't support property restriction via parameters and may be deprecated in future versions. Consider using per-parameter filters instead for better flexibility and performance.
-use App\Filter\SearchFilter;
-use ApiPlatform\Metadata\QueryParameter;
+For existing filters that extend `AbstractFilter` and have pre-configured properties, the parameter's `properties` does **not** restrict the filter's behavior. These filters use their own internal property configuration:
-#[QueryParameter(key: ':property', filter: SearchFilter::class)]
-class Book {
- public string $id;
- public string $title;
- public Author $author;
-}
+```php
+ new QueryParameter(
+ properties: ['title', 'author'], // Only affects _properties, doesn't restrict filter
+ filter: new SearchFilter(properties: ['title' => 'partial', 'description' => 'partial'])
+)
+
+// To restrict legacy filters, configure them with only the desired properties:
+'search[:property]' => new QueryParameter(
+ filter: new SearchFilter(properties: ['title' => 'partial', 'author' => 'exact'])
+)
```
-This will declare a query parameter for each property (ID, title and author) calling the SearchFilter.
+#### 2. Per-Parameter Filters (Recommended)
+
+> [!NOTE]
+> Per-parameter filters are the modern approach. They provide better performance (only process requested properties), cleaner code, and full support for parameter-based property restriction.
-This is especially useful for sort filters where you'd like to use `?sort[name]=asc`:
+Modern filters that work on a per-parameter basis can be effectively restricted using the parameter's `properties`:
```php
-namespace App\ApiResource;
+getValue();
+
+ // Get the property for this specific parameter
+ $property = $parameter->getProperty();
+ $alias = $queryBuilder->getRootAliases()[0];
+ $field = $alias.'.'.$property;
+
+ $parameterName = $queryNameGenerator->generateParameterName($property);
+
+ $queryBuilder
+ ->andWhere($queryBuilder->expr()->like('LOWER('.$field.')', ':'.$parameterName))
+ ->setParameter($parameterName, '%'.strtolower($value).'%');
+ }
+}
+```
-#[QueryParameter(key: 'sort[:property]', filter: OrderFilter::class)]
+```php
+ new QueryParameter(
+ properties: ['title', 'author'], // Only these properties get parameters created
+ filter: new PartialSearchFilter()
+ )
+ ]
+ )
+])]
class Book {
- public string $id;
- public string $title;
- public Author $author;
+ // ...
}
```
-### Documentation
+**How it works:**
+1. API Platform creates individual parameters: `search[title]` and `search[author]` only
+2. URLs like `/books?search[description]=foo` are ignored (no parameter exists)
+3. Each parameter calls the filter with its specific property via `$parameter->getProperty()`
+4. The filter processes only that one property
-A parameter is quite close to its documentation, and you can specify the JSON Schema and/or the OpenAPI documentation:
+This approach is recommended for new filters as it's more flexible and allows true property restriction via the parameter configuration.
-```php
-namespace App\ApiResource;
+Note that invalid values are usually ignored by our filters, use [validation](#parameter-validation) to trigger errors for wrong parameter values.
-use ApiPlatform\Metadata\QueryParameter;
-use ApiPlatform\OpenApi\Model\Parameter;
+## OpenAPI and JSON Schema
-#[QueryParameter(
- key: 'q',
- required: true,
- schema: ['type' => 'string'],
- openApi: new Parameter(in: 'query', name: 'q', allowEmptyValue: true)
-)]
-class Book {
- public string $id;
- public string $title;
- public Author $author;
-}
-```
+You have full control over how your parameters are documented in OpenAPI.
-### Filter aliasing
+### Customizing the OpenAPI Parameter
-Filter aliasing is done by declaring a parameter key with a different property:
+You can pass a fully configured `ApiPlatform\OpenApi\Model\Parameter` object to the `openApi` property of your parameter attribute. This gives you total control over the generated documentation.
```php
-#[GetCollection(
- parameters: [
- 'fooAlias' => new QueryParameter(filter: 'app_search_filter_via_parameter', property: 'foo'),
- ]
-)]
-class Book {
- public string $id;
- public string $foo;
-}
+ new QueryParameter(
+ schema: ['enum' => ['a', 'b'], 'uniqueItems' => true],
+ castToArray: true,
+ openApi: new OpenApiParameter(name: 'enum', in: 'query', style: 'deepObject')
+ )
+ ]
+ )
+])]
+class User {}
```
-If you need you can use the `filterContext` to transfer information between a parameter and its filter.
+### Using JSON Schema and Type Casting
-### Parameter validation
+The `schema` property allows you to define validation rules using JSON Schema keywords. This is useful for simple validation like ranges, patterns, or enumerations.
-If you use Laravel refers to the [Laravel Validation documentation](../laravel/validation.md).
-
-Parameter validation is automatic based on the configuration for example:
+When you define a `schema`, API Platform can often infer the native PHP type of the parameter. For instance, `['type' => 'boolean']` implies a boolean. If you want to ensure the incoming string value (e.g., "true", "0") is cast to its actual native type before validation and filtering, set `castToNativeType` to `true`.
```php
new QueryParameter(schema: ['enum' => ['a', 'b'], 'uniqueItems' => true]),
- 'num' => new QueryParameter(schema: ['minimum' => 1, 'maximum' => 3]),
- 'exclusiveNum' => new QueryParameter(schema: ['exclusiveMinimum' => 1, 'exclusiveMaximum' => 3]),
- 'blank' => new QueryParameter(openApi: new OpenApiParameter(name: 'blank', in: 'query', allowEmptyValue: false)),
- 'length' => new QueryParameter(schema: ['maxLength' => 1, 'minLength' => 3]),
- 'array' => new QueryParameter(schema: ['minItems' => 2, 'maxItems' => 3]),
- 'multipleOf' => new QueryParameter(schema: ['multipleOf' => 2]),
- 'pattern' => new QueryParameter(schema: ['pattern' => '/\d/']),
- 'required' => new QueryParameter(required: true),
- ],
-)]
-class ValidateParameter {}
+#[ApiResource(operations: [
+ new GetCollection(
+ uriTemplate: '/settings',
+ parameters: [
+ 'isEnabled' => new QueryParameter(
+ schema: ['type' => 'boolean'],
+ castToNativeType: true
+ )
+ ]
+ )
+])]
+class Setting {}
```
-You can also use your own constraint by setting the `constraints` option on a Parameter. In that case we won't set up the automatic validation for you, and it'll replace our defaults.
-
-### Parameter security
+If you need a custom validation function use the `castFn` property of the `Parameter` class.
-If you use Laravel refers to the [Laravel Security documentation](../laravel/security.md).
+## Parameter Validation
-Parameters may have security checks:
+You can enforce validation rules on your parameters using the `required` property or by attaching Symfony Validator constraints.
```php
new QueryParameter(security: 'is_granted("ROLE_ADMIN")'),
- 'auth' => new HeaderParameter(security: '"secretKey" == auth[0]'),
- ],
-)]
-class SecurityParameter {}
+use Symfony\Component\Validator\Constraints as Assert;
+
+#[ApiResource(operations: [
+ new GetCollection(
+ uriTemplate: '/users/validate',
+ parameters: [
+ 'country' => new QueryParameter(
+ description: 'Filter by country code.',
+ constraints: [new Assert\Country()]
+ ),
+ 'X-Request-ID' => new HeaderParameter(
+ description: 'A unique request identifier.',
+ required: true,
+ constraints: [new Assert\Uuid()]
+ )
+ ]
+ )
+])]
+class User {}
```
-## Serializer Filters
+Note that when `castToNativeType` is enabled, API Platform infers type validation from the JSON Schema.
+
+Here is the list of validation constraints that are automatically inferred from the JSON Schema and OpenAPI definitions of a parameter.
+
+### From OpenAPI Definition
-### Group Filter
+* **`allowEmptyValue`**: If set to `false`, a `Symfony\Component\Validator\Constraints\NotBlank` constraint is added.
-The group filter allows you to filter by serialization groups.
+### From JSON Schema (`schema` property)
-Syntax: `?groups[]=`
+* **`minimum`** / **`maximum`**:
+ * If both are set, a `Symfony\Component\Validator\Constraints\Range` constraint is added.
+ * If only `minimum` is set, a `Symfony\Component\Validator\Constraints\GreaterThanOrEqual` constraint is added.
+ * If only `maximum` is set, a `Symfony\Component\Validator\Constraints\LessThanOrEqual` constraint is added.
+* **`exclusiveMinimum`** / **`exclusiveMaximum`**:
+ * If `exclusiveMinimum` is used, it becomes a `Symfony\Component\Validator\Constraints\GreaterThan` constraint.
+ * If `exclusiveMaximum` is used, it becomes a `Symfony\Component\Validator\Constraints\LessThan` constraint.
+* **`pattern`**: Becomes a `Symfony\Component\Validator\Constraints\Regex` constraint.
+* **`minLength`** / **`maxLength`**: Becomes a `Symfony\Component\Validator\Constraints\Length` constraint.
+* **`multipleOf`**: Becomes a `Symfony\Component\Validator\Constraints\DivisibleBy` constraint.
+* **`enum`**: Becomes a `Symfony\Component\Validator\Constraints\Choice` constraint with the specified values.
+* **`minItems`** / **`maxItems`**: Becomes a `Symfony\Component\Validator\Constraints\Count` constraint (for arrays).
+* **`uniqueItems`**: If `true`, becomes a `Symfony\Component\Validator\Constraints\Unique` constraint (for arrays).
+* **`type`**:
+ * If set to `'array'`, a `Symfony\Component\Validator\Constraints\Type('array')` constraint is added.
+ * If `castToNativeType` is also `true`, the schema `type` will add a `Symfony\Component\Validator\Constraints\Type` constraint for `'boolean'`, `'integer'`, and `'number'` (as `float`).
-You can add as many groups as you need.
+### From the Parameter's `required` Property
-Enable the filter:
+* **`required`**: If set to `true`, a `Symfony\Component\Validator\Constraints\NotNull` constraint is added.
+
+### Strict Parameter Validation
+
+By default, API Platform allows clients to send extra query parameters that are not defined in the operation's `parameters`. To enforce a stricter contract, you can set `strictQueryParameterValidation` to `true` on an operation. If an unsupported parameter is sent, API Platform will return a 400 Bad Request error.
```php
'groups', 'overrideDefaultGroups' => false, 'whitelist' => ['allowed_group']])]
-class Book
-{
- // ...
-}
+#[ApiResource(operations: [
+ new Get(
+ uriTemplate: 'strict_query_parameters',
+ strictQueryParameterValidation: true,
+ parameters: [
+ 'foo' => new QueryParameter(),
+ ]
+ )
+])]
+class StrictParameters {}
```
-Three arguments are available to configure the filter:
+With this configuration, a request to `/strict_query_parameters?bar=test` will fail with a 400 error because `bar` is not a supported parameter.
-- `parameterName` is the query parameter name (default `groups`)
-- `overrideDefaultGroups` allows to override the default serialization groups (default `false`)
-- `whitelist` groups whitelist to avoid uncontrolled data exposure (default `null` to allow all groups)
+## Parameter Providers
-Given that the collection endpoint is `/books`, you can filter by serialization groups with the following query: `/books?groups[]=read&groups[]=write`.
+Parameter Providers are powerful services that can inspect, transform, or provide values for parameters. They can even modify the current `Operation` metadata on the fly. A provider is a class that implements `ApiPlatform\State\ParameterProviderInterface`.
-### Property filter
+### `IriConverterParameterProvider`
+
+This built-in provider takes an IRI string (e.g., `/users/1`) and converts it into the corresponding Doctrine entity object. It supports both single IRIs and arrays of IRIs.
+
+```php
+ new QueryParameter(provider: IriConverterParameterProvider::class),
+ 'related' => new QueryParameter(
+ provider: IriConverterParameterProvider::class,
+ extraProperties: ['fetch_data' => true] // Forces fetching the entity data
+ ),
+ ],
+ provider: [self::class, 'provideDummyFromParameter'],
+ )
+])]
+class WithParameter
+{
+ public static function provideDummyFromParameter(Operation $operation, array $uriVariables = [], array $context = []): object|array
+ {
+ // The value has been transformed from an IRI to an entity by the provider.
+ $dummy = $operation->getParameters()->get('dummy')->getValue();
+
+ // If multiple IRIs were provided as an array, this will be an array of entities
+ $related = $operation->getParameters()->get('related')->getValue();
+
+ return $dummy;
+ }
+}
+```
-**Note:** We strongly recommend using [Vulcain](https://vulcain.rocks) instead of this filter.
-Vulcain is faster, allows a better hit rate, and is supported out of the box in the API Platform distribution.
+### Configuration Options
-The property filter adds the possibility to select the properties to serialize (sparse fieldsets).
+The `IriConverterParameterProvider` supports the following options in `extraProperties`:
-Syntax: `?properties[]=&properties[][]=`
+- **`fetch_data`**: Boolean (default: `false`) - When `true`, forces the IRI converter to fetch the actual entity data instead of just creating a reference.
-You can add as many properties as you need.
+### `ReadLinkParameterProvider`
-Enable the filter:
+This provider fetches a linked resource from a given identifier. This is useful when you need to load a related entity to use later, for example in your own state provider.
+When you have an API resource with a custom `uriTemplate` that includes parameters, the `ReadLinkParameterProvider` can automatically resolve the linked resource using the operation's URI template. This is particularly useful for nested resources or when you need to load a parent resource based on URI variables.
```php
'properties', 'overrideDefaultProperties' => false, 'whitelist' => ['allowed_property']])]
-class Book
+#[Get(
+ uriTemplate: 'with_parameters/{id}{._format}',
+ uriVariables: [
+ 'id' => new Link(schema: ['type' => 'string', 'format' => 'uuid'], property: 'id'),
+ ],
+ parameters: [
+ 'dummy' => new QueryParameter(
+ provider: ReadLinkParameterProvider::class,
+ extraProperties: [
+ 'resource_class' => Dummy::class,
+ 'uri_template' => '/dummies/{id}' // Optional: specify the template for the linked resource
+ ]
+ )
+ ],
+ provider: [self::class, 'provideDummyFromParameter'],
+)]
+class WithParameter
{
- // ...
+ public static function provideDummyFromParameter(Operation $operation, array $uriVariables = [], array $context = []): object|array
+ {
+ // The dummy parameter has been resolved to the actual Dummy entity
+ // based on the parameter value and the specified uri_template
+ return $operation->getParameters()->get('dummy')->getValue();
+ }
}
```
-Three arguments are available to configure the filter:
+The provider will:
+- Take the parameter value (e.g., a UUID or identifier)
+- Use the `resource_class` to determine which resource to load
+- Optionally use the `uri_template` from `extraProperties` to construct the proper operation for loading the resource
+- Return the loaded entity, making it available in your state provider
-- `parameterName` is the query parameter name (default `properties`)
-- `overrideDefaultProperties` allows to override the default serialization properties (default `false`)
-- `whitelist` properties whitelist to avoid uncontrolled data exposure (default `null` to allow all properties)
+#### Array Support
-Given that the collection endpoint is `/books`, you can filter the serialization properties with the following query: `/books?properties[]=title&properties[]=author`.
-If you want to include some properties of the nested "author" document, use: `/books?properties[]=title&properties[author][]=name`.
+Both `IriConverterParameterProvider` and `ReadLinkParameterProvider` support processing arrays of values. When you pass an array of identifiers or IRIs, they will return an array of resolved entities:
-## Creating Custom Filters
+```php
+// For IRI converter: ?related[]=/dummies/1&related[]=/dummies/2
+// For ReadLink provider: ?dummies[]=uuid1&dummies[]=uuid2
+'items' => new QueryParameter(
+ provider: ReadLinkParameterProvider::class,
+ extraProperties: ['resource_class' => Dummy::class]
+)
+```
-Custom filters can be written by implementing the `ApiPlatform\Metadata\FilterInterface` interface.
+### ReadLinkParameterProvider Configuration Options
-API Platform provides a convenient way to create Doctrine ORM and MongoDB ODM filters. If you use [custom state providers](state-providers.md),
-you can still create filters by implementing the previously mentioned interface, but - as API Platform isn't aware of your
-persistence system's internals - you have to create the filtering logic by yourself.
+You can control the behavior of `ReadLinkParameterProvider` with these `extraProperties`:
-If you need more information about creating custom filters, refer to the following documentation:
+- **`resource_class`**: The class of the resource to load
+- **`uri_template`**: Optional URI template for the linked resource operation
+- **`uri_variable`**: Name of the URI variable to use when building URI variables array
+- **`throw_not_found`**: Boolean (default: `true`) - Whether to throw `NotFoundHttpException` when resource is not found
-- [Creating Custom Doctrine ORM filters](../core/doctrine-filters.md#creating-custom-doctrine-orm-filters)
-- [Creating Custom Doctrine Mongo ODM filters](../core/doctrine-filters.md#creating-custom-doctrine-mongodb-odm-filters)
-- [Creating Custom Elasticsearch Filters](../core/elasticsearch-filters.md#creating-custom-elasticsearch-filters)
+```php
+'dummy' => new QueryParameter(
+ provider: ReadLinkParameterProvider::class,
+ extraProperties: [
+ 'resource_class' => Dummy::class,
+ 'throw_not_found' => false, // Won't throw NotFoundHttpException if resource is missing
+ 'uri_variable' => 'customId' // Use 'customId' as the URI variable name
+ ]
+)
+```
-## ApiFilter Attribute
+### Creating a Custom Parameter Provider
-The attribute can be used on a `property` or on a `class`.
+You can create your own providers to implement any custom logic. A provider must implement `ParameterProviderInterface`. The `provide` method can modify the parameter's value or even return a modified `Operation` to alter the request handling flow.
-If the attribute is given over a property, the filter will be configured on the property. For example, let's add a search filter on `name` and on the `prop` property of the `colors` relation:
+For instance, a provider could add serialization groups to the normalization context based on a query parameter:
```php
'ipartial'])]
- public Collection $colors;
-
- public function __construct()
+ public function provide(Parameter $parameter, array $parameters = [], array $context = []): ?Operation
{
- $this->colors = new ArrayCollection();
+ $operation = $context['operation'] ?? null;
+ if (!$operation) {
+ return null;
+ }
+
+ $value = $parameter->getValue();
+ if ('extended' === $value) {
+ $context = $operation->getNormalizationContext();
+ $context[AbstractNormalizer::GROUPS][] = 'extended_read';
+ return $operation->withNormalizationContext($context);
+ }
+
+ return $operation;
}
-
- // ...
}
```
-On the first property, `name`, it's straightforward. The first attribute argument is the filter class, the second specifies options, here, the strategy:
-
-```php
-#[ApiFilter(SearchFilter::class, strategy: 'partial')]
-```
+### Changing how to parse Query / Header Parameters
-In the second attribute, we specify `properties` to which the filter should apply. It's necessary here because we don't want to filter `colors` but the `prop` property of the `colors` association.
-Note that for each given property we specify the strategy:
+We use our own algorithm to parse a request's query, if you want to do the parsing of `QUERY_STRING` yourself, set `_api_query_parameters` in the Request attributes (`$request->attributes->set('_api_query_parameters', [])`) yourself.
+By default we use Symfony's `$request->headers->all()`, you can also set `_api_header_parameters` if you want to parse them yourself.
-```php
-#[ApiFilter(SearchFilter::class, properties: ['colors.prop' => 'ipartial'])]
-```
+## Creating Custom Filters
-The `ApiFilter` attribute can be set on the class as well. If you don't specify any properties, it'll act on every property of the class.
+For data-provider-specific filtering (e.g., Doctrine ORM), the recommended way to create a filter is to implement the corresponding `FilterInterface`.
-For example, let's define three data filters (`DateFilter`, `SearchFilter` and `BooleanFilter`) and two serialization filters (`PropertyFilter` and `GroupFilter`) on our `DummyCar` class:
+For Doctrine ORM, your filter should implement `ApiPlatform\Doctrine\Orm\Filter\FilterInterface`:
```php
'ipartial', 'name' => 'partial'])]
-#[ApiFilter(PropertyFilter::class, arguments: ['parameterName' => 'foobar'])]
-#[ApiFilter(GroupFilter::class, arguments: ['parameterName' => 'foobargroups'])]
-class DummyCar
+final class RegexpFilter implements FilterInterface
{
- // ...
-}
+ public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
+ {
+ $parameter = $context['parameter'] ?? null;
+ $value = $parameter?->getValue();
+
+ // The parameter may not be present.
+ // It's recommended to add validation (e.g., `required: true`) on the Parameter attribute
+ // if the filter logic depends on the value.
+ if ($value instanceof ParameterNotFound) {
+ return;
+ }
+
+ $alias = $queryBuilder->getRootAliases()[0];
+ $parameterName = $queryNameGenerator->generateParameterName('regexp_name');
+
+ // Access the parameter's property or use the parameter key as fallback
+ $property = $parameter->getProperty() ?? $parameter->getKey() ?? 'name';
+
+ // You can also access filter context if the parameter provides it
+ $filterContext = $parameter->getFilterContext() ?? null;
+
+ $queryBuilder
+ ->andWhere(sprintf('REGEXP(%s.%s, :%s) = 1', $alias, $property, $parameterName))
+ ->setParameter($parameterName, $value);
+ }
+ // For BC, this function is not useful anymore when documentation occurs on the Parameter
+ public function getDescription(): array {
+ return [];
+ }
+}
```
-The `BooleanFilter` is applied to every `Boolean` property of the class. Indeed, in each core filter, we check the Doctrine type. It's written only by using the filter class:
+You can then instantiate this filter directly in your `QueryParameter`:
```php
-#[ApiFilter(BooleanFilter::class)]
+ new QueryParameter(filter: new RegexpFilter())
+ ]
+ )
+])]
+class User {}
```
-The `DateFilter` given here will be applied to every `Date` property of the `DummyCar` class with the `DateFilterInterface::EXCLUDE_NULL` strategy:
+### Advanced Use Case: Composing Filters
+
+You can create complex filters by composing existing ones. This is useful when you want to apply multiple filtering logics based on a single parameter.
```php
-#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
+getValue();
+ if ($value instanceof ParameterNotFound) {
+ return;
+ }
+
+ // Create a new context for the sub-filters, passing the value.
+ $subContext = ['filters' => ['searchOnTextAndDate' => $value]] + $context;
+
+ $this->searchFilter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, $subContext);
+ $this->dateFilter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, $subContext);
+ }
+}
```
-The `SearchFilter` here adds properties. The result is the exact same as the example with attributes on properties:
+To use this composite filter, register it as a service and reference it by its ID:
+```yaml
+# config/services.yaml
+services:
+ 'app.filter_date_and_search':
+ class: App\Filter\SearchTextAndDateFilter
+ autowire: true
```php
-#[ApiFilter(SearchFilter::class, properties: ['colors.prop' => 'ipartial', 'name' => 'partial'])]
+ new QueryParameter(filter: 'app.filter_date_and_search')
+ ]
+ )
+])]
+class LogEntry {}
```
-Note that you can specify the `properties` argument on every filter.
+## Parameter Attribute Reference
+
+| Property | Description |
+|---|---|
+| `key` | The name of the parameter (e.g., `name`, `order`). |
+| `filter` | The filter service or instance that processes the parameter's value. |
+| `provider` | A service that transforms the parameter's value before it's used. |
+| `description` | A description for the API documentation. |
+| `property` | The resource property this parameter is mapped to. |
+| `required` | Whether the parameter is required. |
+| `constraints` | Symfony Validator constraints to apply to the value. |
+| `schema` | A JSON Schema for validation and documentation. |
+| `castToArray` | Casts the parameter value to an array. Useful for query parameters like `foo[]=1&foo[]=2`. Defaults to `true`. |
+| `castToNativeType` | Casts the parameter value to its native PHP type based on the `schema`. |
+| `openApi` | Customize OpenAPI documentation or hide the parameter (`false`). |
+| `hydra` | Hide the parameter from Hydra documentation (`false`). |
+| `security` | A [Symfony expression](https://symfony.com/doc/current/security/expressions.html) to control access to the parameter. |
-The next filters are not related to how the data is fetched but rather to how the serialization is done on those. We can give an `arguments` option ([see here for the available arguments](#serializer-filters)):
+## Parameter Security
+
+You can secure individual parameters using Symfony expression language. When a security expression evaluates to `false`, the parameter will be ignored and treated as if it wasn't provided.
```php
-#[ApiFilter(PropertyFilter::class, arguments: ['parameterName' => 'foobar'])]
-#[ApiFilter(GroupFilter::class, arguments: ['parameterName' => 'foobargroups'])]
+ new QueryParameter(
+ security: 'is_granted("ROLE_ADMIN")'
+ ),
+ 'auth' => new HeaderParameter(
+ security: '"secured" == auth',
+ description: 'Only accessible when auth header equals "secured"'
+ ),
+ 'secret' => new QueryParameter(
+ security: '"secured" == secret',
+ description: 'Only accessible when secret parameter equals "secured"'
+ )
+ ]
+ )
+])]
+class SecureResource
+{
+ // ...
+}
```
+
+In the security expressions, you have access to:
+- Parameter values by their key name (e.g., `auth`, `secret`)
+- Standard security functions like `is_granted()`
+- The current user via `user`
+- Request object via `request`
+