Skip to main content
Version: 2025.4

Create a Custom Frontend Adapter

Every entry in the adapter mapping pairs a frontend adapter with a backend adapter. The frontend adapter decides which input controls the tree level configuration renders for that data type; the backend adapter resolves the value used to build the tree.

The bundle ships four frontend adapters: SimpleValueAdapter, LocalizedFieldAdapter, RelationAdapter and ObjectBrickAdapter. Custom frontend adapters are registered differently in Pimcore Studio and in the Admin Classic UI, but both use the same frontend name in the mapping.

Pimcore Studio​

Frontend adapters are registered in the frontend adapter registry, a service the bundle binds to the Studio UI container under the id BackendPowerTools/AetConfiguration/FrontendAdapterRegistry. Your Studio UI bundle can add its own adapter in three steps:

  1. Create a React component that renders the controls;
  2. Register the component in the frontend adapter registry;
  3. Add the adapter to the mapping.

Create the Component​

An adapter is a plain React component. It renders additional Form.Items for one tree level and receives the tree level context as props:

PropDescription
classIdId of the data-object class the tree is built for.
classFieldNameName of the class field selected for this tree level.
classFieldThe selected class field as delivered by the backend, including its additionalData.
adapterConfigurationThe adapter configuration currently stored for this tree level.

Every control must live under the adapterConfiguration form path. Whatever the controls collect is stored as the adapter configuration of the tree level and passed to the backend adapter's setConfiguration(). Values must be scalars (string, number or boolean); the backend configuration schema rejects arrays and nested objects, so a multi-select has to be stored as e.g. a comma-separated string.

import React from 'react'
import { useTranslation } from '@pimcore/studio-ui-bundle/app'
import { Form, Input } from '@pimcore/studio-ui-bundle/components'

export interface AetFrontendAdapterProps {
classId: string
classFieldName: string
classField?: { name: string, title: string, fieldType: string, adapterType: string, additionalData?: unknown }
adapterConfiguration?: Record<string, string | number | boolean | null | undefined>
}

export const FooAdapter = (props: AetFrontendAdapterProps): React.JSX.Element => {
const { t } = useTranslation()

return (
<>
<Form.Item
label={ t('aet-configuration.tree-level.icon-class') }
name={ ['adapterConfiguration', 'treeLevelIconClass'] }
>
<Input />
</Form.Item>

<Form.Item
label="Placeholder"
name={ ['adapterConfiguration', 'placeholder'] }
>
<Input />
</Form.Item>
</>
)
}

Register the Component​

Register the adapter in the onInit hook of one of your bundle's Studio UI modules. The registry is bound and seeded with the built-in adapters while the Backend Power Tools plugin initializes, which happens before any module's onInit runs. Registering and overriding therefore behave the same regardless of the order in which the bundles' modules run.

import type React from 'react'
import { container, type AbstractModule } from '@pimcore/studio-ui-bundle'
import { FooAdapter, type AetFrontendAdapterProps } from './foo-adapter'

interface AetFrontendAdapterRegistry {
registerAdapter: (adapter: { type: string, component: React.ComponentType<AetFrontendAdapterProps> }) => void
overrideAdapter: (adapter: { type: string, component: React.ComponentType<AetFrontendAdapterProps> }) => boolean
hasAdapter: (type: string) => boolean
}

export const FooModule: AbstractModule = {
onInit: (): void => {
const registry = container.get<AetFrontendAdapterRegistry>('BackendPowerTools/AetConfiguration/FrontendAdapterRegistry')

registry.registerAdapter({
type: 'FooAdapter', //= reflects the "frontend" adapter in the config
component: FooAdapter
})
}
}

registerAdapter() refuses to register a type twice and reports the collision through the Studio error handler, so one bundle cannot silently replace another bundle's adapter. To replace a registered adapter on purpose — for example to take over one of the built-in adapters — call overrideAdapter() instead.

Add the Adapter to the Mapping​

The last step is to add the adapter to the mapping in the config.yaml of your bundle. The string provided to frontend has to match the type you registered.

pimcore_backend_power_tools:
pimcore_alternative_object_trees:
adapters:
input: #= the datatype of the corresponding class field
frontend: FooAdapter
backend: Pimcore\Bundle\BackendPowerToolsBundle\Adapter\AlternativeElementTree\DataObject\SimpleValueAdapter
info

If the frontend value names a type that no bundle registered, the tree level renders the class field selector only and no adapter-specific controls.

Admin Classic UI​

To create a custom frontend adapter for the Admin Classic UI, you need to follow three steps:

  1. Create a new JS class for your custom frontend adapter;
  2. Register the class via an event;
  3. Add the adapter to the mapping.

Create the JS class​

Each adapter has to extend the pimcore.bundle.backendpowertools.alternativeelementtree.configuration.adapter.abstract class.

The only thing you have to do is to set your adapters type and implement the setFields method. This method has to return an array of ExtJS fields.

pimcore.registerNS("your.custom.adapter.fooAdapter");

your.custom.adapter.fooAdapter = Class.create(
pimcore.bundle.backendpowertools.alternativeelementtree.configuration.adapter.abstract, {
type: 'FooAdapter',

setFields: function () {
this.fields = [
this.getTextField()
];

return this.fields;
},

getTextField: function () {
return new Ext.form.TextField({
name: 'placeholder',
fieldLabel: "Placeholder",
value: this.config.placeholder ?? null
});
}
});

your.custom.adapter.fooAdapter.type = 'FooAdapter'; //reflects the "frontend" adapter in the config

Register the Class via an Event​

After creating the class itself, you must register it via the pimcore.backendpowertools.adt.registerAdtAdapter event.

...

initialize: function () {
document.addEventListener(pimcore.bundle.backendpowertools.events.registerAetAdapter, this.registerAdapters.bind(this));
},

registerAdapters: function (event) {
const adapters = event.detail.adapters;
adapters.push(your.custom.adapter.fooAdapter);

...

Add the Adapter to the Mapping​

The last step is to add the adapter to the mapping. This can be done in the config.yaml of your bundle. Be aware that the string provided to frontend has to match the type of your adapter.

pimcore_backend_power_tools:
pimcore_alternative_object_trees:
adapters:
input: #= the datatype of the corresponding class field
frontend: FooAdapter
backend: SimpleValueAdapter