Skip to main content
Version: 2026.2

Frontend Adapters

Every entry in the adapter mapping pairs a frontend adapter with a backend adapter. The frontend adapter decides which input controls the Tree Levels tab 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.

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.