Custom view model builders 

Every EXT:form renderable that reaches HBS_RENDERABLES is converted to a view model before TypoScript processes it. The conversion is handled by view model builders: classes that implement \CPSIT\Typo3HandlebarsForms\Domain\ViewModel\Builder\ViewModelBuilder.

When no registered builder claims a renderable, a plain SimpleViewModel is used as the fallback.

Implementing the interface 

The ViewModelBuilder interface declares two methods:

interface ViewModelBuilder
{
    public function build(
        RootRenderableInterface $renderable,
        RenderingContext $renderingContext,
    ): ViewModel;

    public function supports(RootRenderableInterface $renderable): bool;
}
Copied!

supports() is called first; return true only for the renderable types your builder handles. build() is then called to produce the view model.

The easiest starting point is to extend \CPSIT\Typo3HandlebarsForms\Domain\ViewModel\Builder\AbstractViewModelBuilder. It takes care of the boilerplate:

  • Wraps the rendering inside a simulated <formvh:renderRenderable> context so EXT:form's state is correct.
  • Applies grid-column classes when the renderable is inside a GridRow.
  • Provides renderAdditionalAttributes() to resolve fluidAdditionalAttributes from the element's properties.

Override renderRenderable() to produce your custom view model; return null from it to fall back to the default ViewHelperContainedViewModel that wraps the full rendered output of the view helper.

Minimal example

use CPSIT\Typo3HandlebarsForms;
use TYPO3\CMS\Fluid;
use TYPO3\CMS\Form;

final class RatingViewModelBuilder extends Typo3HandlebarsForms\Domain\ViewModel\Builder\AbstractViewModelBuilder
{
    protected array $supportedTypes = ['Rating'];

    protected function renderRenderable(
        Form\Domain\Model\Renderable\RootRenderableInterface $renderable,
        Fluid\Core\Rendering\RenderingContext $renderingContext,
    ): ?Typo3HandlebarsForms\Domain\ViewModel\ViewModel {
        $result = $this->viewHelperInvoker->invoke(
            $renderingContext,
            Fluid\ViewHelpers\Form\TextfieldViewHelper::class,
            [
                'type'     => 'number',
                'property' => $renderable->getIdentifier(),
                'id'       => $renderable->getUniqueIdentifier(),
                'additionalAttributes' => $this->renderAdditionalAttributes($renderable, $renderingContext),
            ],
        );

        return new Typo3HandlebarsForms\Domain\ViewModel\ViewHelperContainedViewModel($renderable, $result);
    }
}
Copied!

Registration 

The ViewModelBuilder interface carries a #[AutoconfigureTag('handlebars_forms.view_model_builder')] attribute. Any class that implements the interface is therefore registered automatically via Symfony DI when autowiring is enabled (the default for extensions that include a Configuration/Services.yaml with autowire: true).

No explicit YAML service definition is needed.

Builder priority 

When multiple builders claim the same renderable type via supports(), the first one in the service iterator wins. The iterator order is determined by the Symfony DI priority tag attribute. Built-in builders are registered without an explicit priority (i.e. priority 0).

To ensure your builder runs before a built-in one, set a higher priority:

# Configuration/Services.yaml
Vendor\MyExtension\Domain\ViewModel\Builder\RatingViewModelBuilder:
    tags:
        - name: handlebars_forms.view_model_builder
          priority: 10
Copied!

Choosing a view model type 

Pick the view model type that best matches what your TypoScript configuration will consume:

Type When to use
ViewHelperContainedViewModel Wraps the rendered tag from a Fluid view helper. Use when HBS_TAG and HBS_VH_CONTENT need to read from it.
StandaloneTagViewModel Wraps a manually-built TagBuilder. Use for composite elements such as Fieldset where you construct the tag yourself.
FormFieldViewModel Combines a label (as ViewHelperInvocationResult) with a child view model. Use when HBS_LABEL should return the view-helper-resolved label rather than the raw renderable label.
ViewModelCollection Holds a named map of child view models. Use for multi-part elements such as AdvancedPassword or FileUpload where the template needs to access named children. HBS_CHILDREN iterates over these.
SimpleViewModel Thin wrapper over a renderable with no extra state. Use as a fallback or for elements whose properties are accessed entirely via HBS_PROPERTY.