Skip to main content
Version: 2026.2

Extending Ownership Management

The Ownership Management area is an admin-only screen that lets administrators list, reassign the owner of (this is especially useful when the current owner does not exist in the system anymore), and delete user-owned configurations across all users (for example, Grid Configurations or Dashboards).

Add a new type by implementing OwnershipProviderInterface and tagging the service with pimcore.studio_backend.ownership_provider. The implementation may live in any bundle that depends on the Studio Backend Bundle. The Studio Backend auto-discovers tagged providers and exposes them through the generic ownership-management endpoints — no new controllers or routes are required.

How It Works

The generic endpoints resolve the provider by its type (taken from the request path) and delegate to subsequent endpoints.

Implement these methods on your custom provider:

  • getType(): Unique, machine-readable identifier used in the route (e.g. grid_configuration).
  • getLabel(): Translation key (in the studio domain) for the tab label.
  • getIcon(): Icon identifier for the tab.
  • getSortPriority(): Higher values are listed first.
  • listConfigurations(OwnershipListQuery $query): Returns a Collection of OwnershipConfiguration.
  • reassignOwner(array $ids, int $newOwnerId): Reassigns the owner of the given configurations.
  • delete(array $ids): Deletes the given configurations.

Listing

listConfigurations() receives an OwnershipListQuery and must return a Collection<OwnershipConfiguration> (items for the current page plus the total count). The query exposes everything a provider needs, already extracted from the request:

  • getOffset() / getLimit(): pagination window.
  • getSearchTerm(): a single free-text term to match against the configuration name, its id and the owner's username (null when no search is active).
  • includeDeletedOwners(): when false, configurations whose owner no longer exists must be hidden (defaults to true).
  • getSortField() / getSortDirection(): sorting.

Build each row with the OwnershipConfigurationHydrator.

Reusing the in-memory filter

If your configurations reuse the LocationAwareConfigRepository, do not re-implement search, the deleted-owner filter, sorting, and pagination. Hydrate all of your items and delegate to InMemoryCollectionFilterInterface::apply(), which performs the whole pipeline for you.

Owner reassignment and deletion (async)

reassignOwner() and delete() are reused for both the synchronous and asynchronous paths — you do not implement anything extra for batching. The service runs a single id synchronously; for multiple ids it creates a Generic Execution Engine job whose handlers call your provider per batch and report progress. Both methods receive the configuration ids as string[]; cast them to your storage's id type internally.

Example Provider

The example below is an in-memory provider that delegates listing to the shared filter. A real, database-backed example is the GridConfigurationProvider in this bundle, and a configuration-file based example is the DashboardOwnershipProvider in the pimcore/studio-dashboards-bundle.

use Pimcore\Bundle\StudioBackendBundle\OwnershipManagement\Hydrator\OwnershipConfigurationHydratorInterface;
use Pimcore\Bundle\StudioBackendBundle\OwnershipManagement\Provider\OwnershipProviderInterface;
use Pimcore\Bundle\StudioBackendBundle\OwnershipManagement\Query\OwnershipListQuery;
use Pimcore\Bundle\StudioBackendBundle\OwnershipManagement\Service\Filter\InMemoryCollectionFilterInterface;
use Pimcore\Bundle\StudioBackendBundle\Response\Collection;

final readonly class MyConfigurationProvider implements OwnershipProviderInterface
{
private const string TYPE = 'my_configuration';

public function __construct(
private MyConfigurationRepository $repository,
private OwnershipConfigurationHydratorInterface $hydrator,
private InMemoryCollectionFilterInterface $collectionFilter,
) {
}

public function getType(): string
{
return self::TYPE;
}

public function getLabel(): string
{
return 'ownership_management_type_my_configuration';
}

public function getIcon(): string
{
return 'settings';
}

public function getSortPriority(): int
{
return 10;
}

public function listConfigurations(OwnershipListQuery $query): Collection
{
$items = [];
$listItems = $this->repository->findAll($query->getSearchTerm(), $query->getLimit(), $query->getOffset();
foreach ($listItems) as $config) {
$items[] = $this->hydrator->hydrate(
(string) $config->getId(),
self::TYPE,
$config->getName(),
$config->getOwner(),
);
}

return $this->collectionFilter->apply($items, $query);
}

public function reassignOwner(array $ids, int $newOwnerId): void
{
foreach ($ids as $id) {
$config = $this->repository->getById($id);
$config->setOwner($newOwnerId);
$this->repository->save($config);
}
}

public function delete(array $ids): void
{
foreach ($ids as $id) {
$this->repository->delete($this->repository->getById($id));
}
}
}

Register the provider and tag it:

services:
App\OwnershipManagement\MyConfigurationProvider:
tags: [ 'pimcore.studio_backend.ownership_provider' ]

Finally, add the tab label translation key to the studio domain:

# translations/studio.en.yaml
ownership_management_type_my_configuration: My Configurations