Api Platform Conference

The conference dedicated to API Platform and its ecosystem

Sep 21, 22 2023 | Lille & online


API Platform provides a system to extend queries on items and collections.

Extensions are specific to Doctrine and Elasticsearch-PHP, and therefore, the Doctrine ORM / MongoDB ODM support or the Elasticsearch reading support must be enabled to use this feature. If you use custom providers it's up to you to implement your own extension system or not.

You can find a working example of a custom extension using a pagination in the API Platform's demo application.

Custom Doctrine ORM Extension

Custom extensions must implement the ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface and / or the ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface interfaces, to be run when querying for a collection of items and when querying for an item respectively.

If you use custom state providers, they must support extensions and be aware of active extensions to work properly.


In the following example, we will see how to always get the offers owned by the current user. We will set up an exception, whenever the user has the ROLE_ADMIN.

Given these two entities:

// api/src/Entity/User.php
namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;

class User
    // ...
// api/src/Entity/Offer.php
namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;

class Offer
    public User $user;

// api/src/Doctrine/CurrentUserExtension.php

namespace App\Doctrine;

use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Doctrine\Orm\Extension\QueryItemExtensionInterface;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use App\Entity\Offer;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bundle\SecurityBundle\Security;

final class CurrentUserExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface

    public function __construct(private readonly Security $security)

    public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void
        $this->addWhere($queryBuilder, $resourceClass);

    public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, Operation $operation = null, array $context = []): void
        $this->addWhere($queryBuilder, $resourceClass);

    private function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
        if (Offer::class !== $resourceClass || $this->security->isGranted('ROLE_ADMIN') || null === $user = $this->security->getUser()) {

        $rootAlias = $queryBuilder->getRootAliases()[0];
        $queryBuilder->andWhere(sprintf('%s.user = :current_user', $rootAlias));
        $queryBuilder->setParameter('current_user', $user->getId());

Finally, if you're not using the autoconfiguration, you have to register the custom extension with either of those tags:

# api/config/services.yaml

    # ...

            - { name: api_platform.doctrine.orm.query_extension.collection }
            - { name: api_platform.doctrine.orm.query_extension.item }

The api_platform.doctrine.orm.query_extension.collection tag will register this service as a collection extension. The api_platform.doctrine.orm.query_extension.item does the same thing for items.

Note that your extensions should have a positive priority if defined. Internal extensions have negative priorities, for reference:

Service name Priority Class
api_platform.doctrine.orm.query_extension.eager_loading (collection) -8 ApiPlatform\Doctrine\Orm\Extension\EagerLoadingExtension
api_platform.doctrine.orm.query_extension.eager_loading (item) -8 ApiPlatform\Doctrine\Orm\Extension\EagerLoadingExtension
api_platform.doctrine.orm.query_extension.filter -16 ApiPlatform\Doctrine\Orm\Extension\FilterExtension
api_platform.doctrine.orm.query_extension.filter_eager_loading -17 ApiPlatform\Doctrine\Orm\Extension\FilterEagerLoadingExtension
api_platform.doctrine.orm.query_extension.order -32 ApiPlatform\Doctrine\Orm\Extension\OrderExtension
api_platform.doctrine.orm.query_extension.pagination -64 ApiPlatform\Doctrine\Orm\Extension\PaginationExtension

Blocking Anonymous Users

This example adds a WHERE clause condition only when a fully authenticated user without ROLE_ADMIN tries to access a resource. It means that anonymous users will be able to access all data. To prevent this potential security issue, the API must ensure that the current user is authenticated.

To secure the access to endpoints, use the following access control rule:

# app/config/package/security.yaml
    # ...
        # ...
        - { path: ^/offers, roles: IS_AUTHENTICATED_FULLY }
        - { path: ^/users, roles: IS_AUTHENTICATED_FULLY }

Custom Doctrine MongoDB ODM Extension

Creating custom extensions is the same as with Doctrine ORM.

The interfaces are:

  • ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface and ApiPlatform\Doctrine\Odm\Extension\AggregationCollectionExtensionInterface to add stages to the aggregation builder.
  • ApiPlatform\Doctrine\Odm\Extension\AggregationResultItemExtensionInterface and ApiPlatform\Doctrine\Odm\Extension\AggregationResultCollectionExtensionInterface to return a result.

The tags are api_platform.doctrine_mongodb.odm.aggregation_extension.item and api_platform.doctrine_mongodb.odm.aggregation_extension.collection.

The custom extensions receive the aggregation builder, used to execute complex operations on data.

Custom Elasticsearch Extension

Currently only extensions querying for a collection of items through a search request are supported. So your custom extensions must implement the RequestBodySearchCollectionExtensionInterface. Register your custom extensions as services and tag them with the api_platform.elasticsearch.request_body_search_extension.collection tag.

What' new?

API platform conference 2023

Sep 21,22 2023: new edition of our conference dedicated to API Platform and its ecosystem!