Skip to main content
Version: 2026.3

Custom Document Types

Pimcore Studio includes six built-in document types: Email, Folder, Hardlink, Link, Page, and Snippet.

Document Type Adapters​

Document type adapters process document detail data during save/load and API response serialization. Each custom document type maps to an adapter by its type string.

Adding a Custom Adapter​

The example below implements an adapter for the myCustom document type.

1. Register your adapter​

services:
App\Adapter\MyCustomAdapter: ~

2. Implement your adapter​

<?php
declare(strict_types=1);

namespace App\Adapter;

use App\Model\Editable\CustomEditableData;
use App\Model\Settings\CustomSettingsData;
use Pimcore\Bundle\StaticResolverBundle\Models\Element\ServiceResolverInterface;
use Pimcore\Bundle\StudioBackendBundle\Document\Data\EditableDataNormalizerInterface;
use Pimcore\Bundle\StudioBackendBundle\Document\Data\Model\SettingsDataInterface;
use Pimcore\Bundle\StudioBackendBundle\Document\Data\SetterDataInterface;
use Pimcore\Bundle\StudioBackendBundle\Document\Data\SettingsNormalizerInterface;
use Pimcore\Bundle\StudioBackendBundle\Util\Constant\AdapterLoader;
use Pimcore\Bundle\StudioBackendBundle\Util\Constant\Document\DocumentFieldKeys;
use Pimcore\Model\Document\MyCustomType;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;

// Tag with `pimcore.studio_backend.document_type_adapter` (use the AdapterLoader enum).
// The adapter must implement at least `SetterDataInterface` so the system discovers it.
#[AutoconfigureTag(AdapterLoader::DOCUMENT_TYPE_ADAPTER_TAG->value)]
final readonly class MyCustomAdapter implements SetterDataInterface, SettingsNormalizerInterface
{
public function __construct(
private ServiceResolverInterface $serviceResolver
) {
}

// Process and modify data before saving to the document.
public function setData(Document $document, array $data, UserInterface $user): void
{
if (!$document instanceof MyCustomType) {
return;
}

// You can pass your custom document detail data as "editableData" in the `data` array.
if (!isset($data[DocumentFieldKeys::EDITABLE_DATA->value])) {
return;
}

$editableData = $data[DocumentFieldKeys::EDITABLE_DATA->value];

// In this example, we assume that the `assetPath` editable data is a path to some asset.
// We want to resolve this path to the actual asset element and set its ID in the document.
$someDependantElementId = null;
if (isset($editableData['assetPath'])) {
$someAsset = $this->serviceResolver->getElementByPath('asset', $editableData['myCustomProperty']);
$someAssetId = $someAsset?->getId();
}
$document->setRelatedAsset($someAssetId);

// We can also set other properties of the document based on the editable data.
// The editable data can contain any custom data that you want to process.
// Editable data should be passed as an array of key-value pairs, where the key is the property name.
$document->setValues($editableData);

// Likewise, you can also pass custom document settings data as "settingsData" in the array of key-value pairs.
if (!isset($data[DocumentFieldKeys::SETTINGS_DATA->value])) {
return;
}

// This example converts `customUrl` settings data and sets it to a document.
$settings = $data[DocumentFieldKeys::SETTINGS_DATA->value];
$customUrl = $settings['customUrl'] ?? null;
if ($customUrl !== null) {
$settings['customUrl'] = htmlspecialchars($customUrl);
}

$document->setValues($settings);
}

// Normalizes the document settings data before returning it in the detail endpoint.
public function normalizeSettings(Document $document): ?SettingsDataInterface
{
if (!$document instanceof MyCustomType) {
return null;
}

return new CustomSettingsData(
$document->getPropertyOne(),
$document->getPropertyTwo()
);
}


// Normalizes the editable data before returning it in the detail endpoint.
public function normalizeEditableData(Document $document): ?EditableDataNormalizerInterface
{
if (!$document instanceof MyCustomType) {
return null;
}

// Let's assume that the `myCustom` document type is returning only related asset ID.
// However, we want to return multiple attributes of this asset.
$relatedAssetData = null;
$assetId = $document->getRelatedId();
if ($assetId !== null) {
$asset = $this->serviceResolver->getElementById('asset', $assetId);
if ($asset !== null) {
$relatedAssetData = [
'id' => $asset->getId(),
'type' => $asset->getType(),
'fileName' => $asset->getFilename(),
'path' => $asset->getRealFullPath(),
];
}
}

return new CustomEditableData(
$document->getSomeImportantValue(),
$document->getSomeOtherValue(),
$relatedAssetData
);
}
}

It's important that your custom settings data model implements the SettingsDataInterface interface, which is used by the normalizeSettings method.

<?php
declare(strict_types=1);

namespace App\Model\Settings;

use Pimcore\Bundle\StudioBackendBundle\Document\Data\Model\SettingsDataInterface;

final readonly class CustomSettingsData implements SettingsDataInterface
{
public function __construct(
private ?int $customSettingOne,
private ?string $customSettingTwo,
) {
}

public function getCustomSettingOne(): ?int
{
return $this->customSettingOne;
}

public function getCustomSettingTwo(): ?string
{
return $this->customSettingTwo;
}
}

It's important that your custom editable data model implements the EditableDataInterface interface, which is used by the normalizeEditableData method.

<?php
declare(strict_types=1);

namespace App\Model\Editable;

use Pimcore\Bundle\StudioBackendBundle\Document\Data\Model\EditableDataInterface;

final readonly class CustomEditableData implements EditableDataInterface
{
public function __construct(
private ?int $someImportantValue,
private ?string $someOtherValue,
private ?array $relatedAssetData,
) {
}

public function getSomeImportantValue(): ?int
{
return $this->someImportantValue;
}

public function getSomeOtherValue(): ?string
{
return $this->someOtherValue;
}

public function getRelatedAssetData(): ?array
{
return $this->relatedAssetData;
}

3. Add the mapping of the document type and the new adapter​

pimcore_studio_backend:
document_type_adapter_mapping:
App\Adapter\MyCustomAdapter: # The adapter class that should be used for processing of the document type data
- "myCustom" # The document type that should be processed by the adapter

Important interfaces:

  • Pimcore\Bundle\StudioBackendBundle\Document\Data\SetterDataInterface - Mandatory. Every adapter must implement this.

  • Pimcore\Bundle\StudioBackendBundle\Document\Data\EditableDataNormalizerInterface - Implement to normalize editable data for API responses.

  • Pimcore\Bundle\StudioBackendBundle\Document\Data\SettingsNormalizerInterface - Implement to normalize document settings data for API responses.

  • Pimcore\Bundle\StudioBackendBundle\Document\Data\SetInitialDataInterface - Implement to customize initial data on document creation.

  • Pimcore\Bundle\StudioBackendBundle\Document\Data\Model\EditableDataInterface - Your custom editable data model must implement this. Returned by normalizeEditableData of EditableNormalizerInterface.

  • Pimcore\Bundle\StudioBackendBundle\Document\Data\Model\SettingsDataInterface - Your custom settings data model must implement this. Returned by normalizeSettings of SettingsNormalizerInterface.

  • Important data keys:

  • editableData - The key for the detail data of the document (e.g., editable fields of your document).

  • settingsData - The key for the document settings data (e.g., document settings like title, description, prettyUrl).

info

Each adapter has to be tagged with the pimcore.studio_backend.document_type_adapter tag and has to implement the Pimcore\Bundle\StudioBackendBundle\Document\Data\SetterDataInterface interface.