Custom Field Hint Providers
When your bundle introduces custom data types (via ClassDefinition\Data subclasses), the agent won't know what data
format to produce for those fields. Register a custom field hint provider so that get_class_definition and
get_field_schema return correct format hints.
For the architectural picture - the provider pattern, the registry, the recursion depth guard, and the catalog of types the bundle already covers - see Architecture → Field Hint System. Check the catalog first: if your type is already covered by a built-in provider, you do not need a custom one.
When You Need This
- Your bundle adds a custom Pimcore data type (e.g.
colorPicker,currencyField) - An existing type needs a more specific hint than the default
<value>fallback - You want to include custom metadata (valid values, constraints) in the schema
Creating a Provider
Implement FieldFormatHintProviderInterface:
<?php
declare(strict_types=1);
namespace Vendor\Bundle\MyBundle\Mcp\FieldHint;
use Pimcore\Bundle\PimcoreAgentBundle\Mcp\FieldHint\FieldFormatHint;
use Pimcore\Bundle\PimcoreAgentBundle\Mcp\FieldHint\FieldFormatHintProviderInterface;
use Pimcore\Bundle\PimcoreAgentBundle\Mcp\FieldHint\FieldFormatHintRegistry;
use Pimcore\Model\DataObject\ClassDefinition\Data;
final readonly class ColorPickerHintProvider implements FieldFormatHintProviderInterface
{
public function getSupportedFieldTypes(): array
{
return ['colorPicker'];
}
public function getFormatHint(
Data $fieldDef,
FieldFormatHintRegistry $registry
): FieldFormatHint {
return new FieldFormatHint(
editableDataFormat: '"#RRGGBB"',
example: '"#FF6600"',
metadata: ['format' => 'hex color with # prefix']
);
}
}
getSupportedFieldTypes()
Return the Pimcore field type identifiers your provider handles. These must match the string
returned by $fieldDef->getFieldType(). One provider can handle multiple types.
getFormatHint()
Receives the field definition and the registry (for recursive resolution of child fields).
Return a FieldFormatHint with:
| Property | Required | Purpose |
|---|---|---|
editableDataFormat | Yes | Format string the LLM uses to compose data |
example | No | Concrete sample value |
children | No | Nested field schemas (for composite types) |
metadata | No | Constraints, options, valid values |
Registering the Provider
Tag the service with pimcore_agent.field_format_hint_provider:
# config/services.yaml
services:
Vendor\Bundle\MyBundle\Mcp\FieldHint\ColorPickerHintProvider:
tags: [{ name: pimcore_agent.field_format_hint_provider }]
The registry picks up all tagged providers at boot. If multiple providers claim the same field type, the first registered provider wins.
Composite Types with Children
For types that contain nested fields (like blocks or field collections), use the registry to recursively resolve child hints:
public function getFormatHint(
Data $fieldDef,
FieldFormatHintRegistry $registry
): FieldFormatHint {
$children = [];
foreach ($fieldDef->getFieldDefinitions() as $child) {
$hint = $registry->getHint($child);
$children[] = [
'name' => $child->getName(),
'type' => $child->getFieldType(),
'editableDataFormat' => $hint->editableDataFormat,
'example' => $hint->example,
'metadata' => $hint->metadata,
];
}
return new FieldFormatHint(
editableDataFormat: '[{"<field>": <value>, ...}]',
children: $children
);
}
The registry enforces a maximum recursion depth of 4 levels. Beyond that, child fields receive the
generic <value> fallback. You don't need to implement depth checking yourself.
Including Metadata
Metadata helps the LLM make valid choices. Common patterns:
// Enum-like constraints
new FieldFormatHint(
editableDataFormat: '"<status>"',
metadata: ['options' => ['draft', 'review', 'published']]
);
// Numeric constraints
new FieldFormatHint(
editableDataFormat: '<number>',
metadata: ['min' => 0, 'max' => 100, 'decimalPrecision' => 2]
);
// Units
new FieldFormatHint(
editableDataFormat: '{"value": <number>, "unitId": "<unit>"}',
metadata: ['validUnits' => ['kg', 'lb', 'oz'], 'defaultUnit' => 'kg']
);
Overriding Built-in Providers
The registry uses the first registered provider for each field type. Since the bundle's providers
are registered before user bundle providers in the tagged iterator, overriding a built-in provider
requires configuring your service with a higher priority via the priority tag attribute, or
using a compiler pass to replace the built-in provider's service definition.
Testing
Test your provider in isolation by constructing it directly and calling getFormatHint():
public function testColorPickerFormat(): void
{
$provider = new ColorPickerHintProvider();
$fieldDef = $this->createMock(Data::class);
$fieldDef->method('getFieldType')->willReturn('colorPicker');
$registry = $this->createMock(FieldFormatHintRegistry::class);
$hint = $provider->getFormatHint($fieldDef, $registry);
self::assertSame('"#RRGGBB"', $hint->editableDataFormat);
self::assertSame('"#FF6600"', $hint->example);
self::assertArrayHasKey('format', $hint->metadata);
}
For integration testing, verify that get_class_definition returns your format hint for a class
that uses your custom field type.