Creating a custom form element
This tutorial shows you how to create a custom form element for the TYPO3 Form Framework. We'll create a "Gender Select" element as an example.
Table of Contents
Prerequisites
Before you start, make sure you have:
- Basic knowledge of YAML configuration
- A sitepackage where you can add configuration files
Step 1: Create the configuration files
Create a form set directory in your extension. The sub-directory name is
arbitrary — we use Custom here.
File location
Create the following structure in your extension:
EXT:my_extension/
Configuration/
Form/
CustomElement/
config.yaml
Configuration structure
Here's the complete configuration for our Gender Select element:
prototypes:
standard:
formElementsDefinition:
GenderSelect:
implementationClassName: TYPO3\CMS\Form\Domain\Model\FormElements\GenericOptionableFormElement
renderingOptions:
templateName: 'RadioButton'
properties:
options:
f: 'Female'
m: 'Male'
d: 'Diverse'
formEditor:
label: 'Gender Select'
group: select
groupSorting: 9000
iconIdentifier: form-single-select
editors:
100:
identifier: header
templateName: Inspector-FormElementHeaderEditor
200:
identifier: label
templateName: Inspector-TextEditor
# Labels are retrieved from the default language file "EXT:form/Resources/Private/Language/Database.xlf"
# The most keys follow the pattern: formEditor.elements.FormElement.editor.[identifier].[key]
# In this example: "formEditor.elements.FormElement.editor.label.label"
label: formEditor.elements.FormElement.editor.label.label
propertyPath: label
230:
identifier: elementDescription
templateName: Inspector-TextEditor
label: formEditor.elements.FormElement.editor.elementDescription.label
propertyPath: properties.elementDescription
240:
identifier: enabled
templateName: Inspector-CheckboxEditor
label: formEditor.elements.FormElement.editor.enabled.label
propertyPath: renderingOptions.enabled
700:
identifier: gridColumnViewPortConfiguration
templateName: Inspector-GridColumnViewPortConfigurationEditor
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.label
configurationOptions:
viewPorts:
10:
viewPortIdentifier: xs
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.xs.label
20:
viewPortIdentifier: sm
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.sm.label
30:
viewPortIdentifier: md
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.md.label
40:
viewPortIdentifier: lg
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.lg.label
50:
viewPortIdentifier: xl
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.xl.label
60:
viewPortIdentifier: xxl
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.xxl.label
numbersOfColumnsToUse:
label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.numbersOfColumnsToUse.label
propertyPath: 'properties.gridColumnClassAutoConfiguration.viewPorts.{@viewPortIdentifier}.numbersOfColumnsToUse'
description: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.numbersOfColumnsToUse.description
800:
identifier: requiredValidator
templateName: Inspector-RequiredValidatorEditor
label: formEditor.elements.FormElement.editor.requiredValidator.label
validatorIdentifier: NotEmpty
propertyPath: properties.fluidAdditionalAttributes.required
propertyValue: required
configurationOptions:
validationErrorMessage:
label: formEditor.elements.FormElement.editor.requiredValidator.validationErrorMessage.label
propertyPath: properties.validationErrorMessages
description: formEditor.elements.FormElement.editor.requiredValidator.validationErrorMessage.description
errorCodes:
10: 1221560910
20: 1221560718
30: 1347992400
40: 1347992453
9999:
identifier: removeButton
templateName: Inspector-RemoveElementEditor
Common inspector editors
Here are some commonly used inspector editors (Inspector) you can add to your form elements:
- Inspector-FormElementHeaderEditor (100)
- Shows the element header in the inspector panel
- Inspector-TextEditor (200-300)
- A simple text input field for properties like label and description
- Inspector-CheckboxEditor (240)
-
Adds the checkbox Field visible, which sets
rendering. Custom form elements do not get this checkbox automatically. Add the editor to your element to let editors hide it.Options. enabled New in version 14.1 Feature: #108663 - Adjust visibility of form elements in Form Editor
- Inspector-PropertyGridEditor (400)
- A grid editor for managing key-value pairs (like options)
- Inspector-GridColumnViewPortConfigurationEditor (700)
- Controls responsive behavior and column widths for different screen sizes
- Inspector-RequiredValidatorEditor (800)
- Adds a checkbox to make the field required
- Inspector-ValidationErrorMessageEditor (900)
- Allows customizing validation error messages
- Inspector-RemoveElementEditor (9999)
- Shows a button to remove the element from the form
Submitted values in finishers and on the summary page
Changed in version 14.3.7 Important: #106903 - Form element value resolution in finishers
Before, all finisher options used the submitted value of a form element.
The summary page, the email body, and some finisher options show the
display value of a form element instead of the submitted value. For an
element with an options property, the display value is the translated
option label. The Gender Select element shows Female instead of f.
The example uses
\TYPO3\
as implementation. The Core elements Single,
Multi, Radio, and Multi use this class as
well.
See Display values and submitted values for the finisher options that use display values.
Step 2: Register the configuration
No PHP or TypoScript registration is needed. TYPO3 discovers YAML files automatically from any extension that follows the directory convention.
Create a form set with a single config.:
EXT:my_extension/
Configuration/
Form/
CustomElement/
config.yaml ← set metadata + all configuration
Set the priority in config. to a value greater than 10
(the EXT:form core base set) so your configuration is merged on top:
name: my-vendor/custom-element
label: 'My Custom Form Element'
priority: 200
# Form element configuration goes here:
prototypes:
standard:
...
Step 3: Clear Caches
After adding the configuration, you must clear all TYPO3 caches.
Step 4: Using your custom element
Now you can use your custom element in the form editor:
- Open the Form Editor user interface (Forms > [Your Form] > Edit)
- Look for "Gender Select" in the form element browser.
- Add the element to the form.
- Configure the element using the inspector panel on the right.
- Save your form.
- Add a form content element to a page and select the form you just edited.
- Preview the page in the frontend.
The element will now be available in your forms and will render using the RadioButton template in the frontend.
Step 5: Customizing frontend output (optional)
If you want to use a custom template instead of reusing an existing one, follow these steps:
Create custom template
Create your own Fluid template:
EXT:my_extension/Resources/Private/Partials/Form/GenderSelect.fluid.html
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
xmlns:formvh="http://typo3.org/ns/TYPO3/CMS/Form/ViewHelpers"
data-namespace-typo3-fluid="true">
<f:comment>
Custom form element template for a gender selection field.
</f:comment>
<formvh:renderRenderable renderable="{element}">
<f:comment>Render the field wrapper with optional fieldset and legend</f:comment>
<f:render partial="Field/Field"
arguments="{
element: element,
renderFieldset: '{true}',
doNotShowLabel: '{true}'
}"
contentAs="elementContent">
<f:form.validationResults for="{element.rootForm.identifier}.{element.identifier}">
<f:comment>Apply error class if validation errors exist</f:comment>
<f:variable name="errorClass">
{f:if(
condition: '{validationResults.errors}',
then: 'is-invalid'
)}
</f:variable>
<f:comment>Radio button group container</f:comment>
<div id="{element.uniqueIdentifier}"
class="gender-select {errorClass}"
role="radiogroup"
aria-label="{element.label}">
<f:comment>Loop through all available options (e.g., male, female, diverse)</f:comment>
<f:for each="{element.properties.options}" as="label" key="value" iteration="idIterator">
<f:comment>Set ARIA attributes for accessibility</f:comment>
<f:if condition="{element.properties.elementDescription}">
<f:variable name="aria" value="{describedby: '{element.uniqueIdentifier}-desc'}" />
</f:if>
<f:comment>Add error indication to the first radio button if validation fails</f:comment>
<f:if condition="{validationResults.errors} && {idIterator.isFirst}">
<f:variable name="aria" value="{
invalid: 'true',
describedby: '{element.uniqueIdentifier}-errors'
}" />
</f:if>
<f:comment>Individual radio button container</f:comment>
<div class="form-check mb-2">
<label class="form-check-wrapping-label"
for="{element.uniqueIdentifier}-{idIterator.index}">
<f:form.radio
property="{element.identifier}"
id="{element.uniqueIdentifier}-{idIterator.index}"
class="form-check-input"
value="{value}"
errorClass="is-invalid"
additionalAttributes="{formvh:translateElementProperty(
element: element,
property: 'fluidAdditionalAttributes'
)}"
aria="{aria}"
/>
<span class="{element.properties.labelTextClassAttribute}">
{formvh:translateElementProperty(
element: element,
property: '{0: \'options\', 1: value}'
)}
</span>
</label>
</div>
</f:for>
</div>
<f:comment>Display validation errors</f:comment>
<f:if condition="{validationResults.flattenedErrors}">
<span id="{element.uniqueIdentifier}-errors"
role="alert">
<f:for each="{validationResults.errors}" as="error">
<f:format.htmlspecialchars>
{formvh:translateElementError(element: element, error: error)}
</f:format.htmlspecialchars>
<br/>
</f:for>
</span>
</f:if>
</f:form.validationResults>
</f:render>
</formvh:renderRenderable>
</html>
Update configuration
Update your YAML configuration to use the custom partial template:
prototypes:
standard:
formElementsDefinition:
Form:
renderingOptions:
partialRootPaths:
1732785721: 'EXT:my_extension/Resources/Private/Partials/Form/'
GenderSelect:
renderingOptions:
templateName: 'GenderSelect'