Frontend rendering 

Templates 

Fluid templates in the form framework are based on Bootstrap.

Custom templates 

In order to use your own Fluid templates for frontend forms, register your own template paths via YAML in the form configuration (here under the default standard prototype).

prototypes:
  standard:
    formElementsDefinition:
      Form:
        renderingOptions:
          templateRootPaths:
            100: 'EXT:my_site_package/Resources/Private/Frontend/Templates/'
          partialRootPaths:
            100: 'EXT:my_site_package/Resources/Private/Frontend/Partials/'
          layoutRootPaths:
            100: 'EXT:my_site_package/Resources/Private/Frontend/Layouts/'
Copied!

If your form definition then references the standard prototype, the form framework will look for Fluid templates in EXT:my_site_package/Resources/Private/Frontend/[*].

The Form element is the 'main' element. The framework will look for Form.html in templateRootPaths. For all other elements, it will look in partialRootPaths. A partial has the same name as the formElementTypeIdentifier property, for example, a Text template will be in a partial named Text.html in partialRootPaths.

Form element values in finisher templates 

Use the RenderFormValueViewHelper to access form element values in your finisher templates. This ViewHelper accepts a single form element and renders it. The following example shows the RenderFormValueViewHelper being called with two parameters (renderable and as) to output the value of a message field. The value {formValue.processedValue} can then be manipulated with Fluid, styled, etc.

<formvh:renderFormValue renderable="{form.formDefinition.elements.message}" as="formValue">
    {formValue.processedValue}
</formvh:renderFormValue>
Copied!

Names of your form elements can be found in your form definition (in your individual YAML files or in the System > Configuration module if you have the lowlevel extension installed). Or use the debug ViewHelper in Fluid to list all the form elements.

<f:debug>{page.rootForm.elements}</f:debug>
Copied!

Translation 

Translate form definition 

Translation of form definitions works differently to the usual translation of the backend. Currently, there is no graphical user interface for this translation process.

If form definition properties were translated in the same way as the rest of the backend, a backend editor using the form editor to edit a form they would see long unwieldy translation keys. In order to avoid this, form element properties are translated instead of their values. The form framework does not look for translation keys in a translation file. Instead, the system searches for translations of the form element properties independent of their property values. The property values are ignored if an entry is found in a translation file. The form element property values are overridden by the translated values.

This approach is a compromise between two scenarios: creating forms using the form editor or creating form definitions (which could later be edited in the form editor). An editor can create forms just using the form editor where form element property values are displayed in the default language. An integrator can provide additional language files which translate the form depending on the prototype.

Add additional translation files to the form configuration as follows:

prototypes:
  standard:
    formElementsDefinition:
      Form:
        renderingOptions:
          translation:
            translationFiles:
              # custom translation file
              20: 'EXT:my_site_package/Resources/Private/Language/Form/locallang.xlf'
Copied!

The translationFiles array is processed from the highest key to the lowest, i.e. your translation file with key 20 is processed before translation files with key '10' in EXT:form. If no key is found in the translation files, a property value will be displayed unmodified.

The following properties can be translated:

  • label
  • properties.[*]
  • properties.options.[*]
  • properties.fluidAdditionalAttributes.[*]
  • renderingOptions.[*]

The translation keys are put together based on a specific pattern and there is a order (fallback chain) for the translations that depends on translation scenarios. These are the translation scenarios:

  • translation of a form element property for a specific form (formDefinitionIdentifier) and form element (ElementIdentifier`)
  • translation of a form element property for a specific form element (formElementIdentifier) and various forms
  • translation of a form element property for an element type (elementType) and various forms, e.g. the Page element

The look-up process searches for translation keys in all given translation files based on the following order (the same order as the translation scenarios above):

  • <formDefinitionIdentifier>.element.<elementIdentifier>.properties.<propertyName>
  • element.<formElementIdentifier>.properties.<propertyName>
  • element.<elementType>.properties.<propertyName>

Translation of options (properties.options) in form elements, like the Select element, have the following look-up order:

  • <formDefinitionIdentifier>.element.<elementIdentifier>.properties.options.<propertyValue>
  • element.<elementIdentifier>.properties.options.<propertyValue>

Example Form Definition 

identifier: ApplicationForm
type: Form
prototypeName: standard
label: 'Application form'

renderables:
  -
    identifier: GeneralInformation
    type: Page
    label: 'General information'

    renderables:
      -
        identifier: LastName
        type: Text
        label: 'Last name'
        properties:
          placeholder: 'Please enter your last name.'
        defaultValue: ''
      -
        identifier: Software
        type: MultiSelect
        label: 'Known software'
        properties:
          options:
            value1: TYPO3
            value2: Neos
Copied!

In order to translate the form element LastName, the process will look for the following translation keys in the translation files:

  • ApplicationForm.element.LastName.properties.label (<formDefinitionIdentifier>.element.<elementIdentifier>.properties.<propertyName>)
  • element.LastName.properties.label (element.<formElementIdentifier>.properties.<propertyName>)
  • element.Text.properties.label (element.<elementType>.properties.<propertyName>)

If none of these keys exist, 'Last name' will be displayed.

In order to translate the form element Software, the process will look for the following translation keys in the translation files:

  • ApplicationForm.element.Software.properties.label (<formDefinitionIdentifier>.element.<elementIdentifier>.properties.<propertyName>)
  • element.Software.properties.label (element.<formElementIdentifier>.properties.<propertyName>)
  • element.MultiSelect.properties.label (element.<elementType>.properties.<propertyName>)

If none of the these keys exist, 'Known software' will be displayed. The option properties lookup process is as the following:

  • ApplicationForm.element.Software.properties.options.value1 (<formDefinitionIdentifier>.element.<elementIdentifier>.properties.options.<propertyValue>)
  • element.Software.properties.options.value1 (element.<elementIdentifier>.properties.options.<propertyValue>)
  • ApplicationForm.element.Software.properties.options.value2 (<formDefinitionIdentifier>.element.<elementIdentifier>.properties.options.<propertyValue>)
  • element.Software.properties.options.value2 (element.<elementIdentifier>.properties.options.<propertyValue>)

If none of the these keys exist, 'TYPO3' will be displayed as label for the first option and 'Neos' for the second option.

Translation of validation messages 

The translation of validation messages is similar to the translation of form definitions abpve. The same translation files can be used. If the look-up process does not find a key within the files, an Extbase message will be displayed. EXT:form translates validators by default.

The same as for form definitions, the translation keys are put together based on a specific pattern. There is also a fallback chain.

The following translation scenarios are possible:

  • translation of validation messages for a specific validator of a specific form element (elementIdentifier) and specific form (formDefinitionIdentifier)
  • translation of validation messages for a specific validator of various form elements within a specific form (formDefinitionIdentifier)
  • translation of validation messages for a specific validator of a specific form element (elementIdentifier) in various forms
  • translation of validation messages for a specific validator in various forms

In Extbase, validation messages are identified by numerical codes (UNIX timestamps). Different codes can be used for the same validator. Read more about concrete validator configurations.

The look-up process searches for translation keys in the translation files in the following order (the same order as the translation scenarios above):

  • <formDefinitionIdentifier>.validation.error.<elementIdentifier>.<validationErrorCode>
  • <formDefinitionIdentifier>.validation.error.<validationErrorCode>
  • validation.error.<elementIdentifier>.<validationErrorCode>
  • validation.error.<validationErrorCode>

Example Form Definition with Validator 

identifier: ContactForm
type: Form
prototypeName: standard
label: 'Contact us'

renderables:
  -
    identifier: Page1
    type: Page
    label: 'Page 1'

    renderables:
      -
        identifier: LastName
        type: Text
        label: 'Last name'
        properties:
          fluidAdditionalAttributes:
            required: required
        validators:
          -
            identifier: NotEmpty
Copied!

If a user submits this form without providing a last name, the NotEmpty validator (at the bottom of the example above) fails and sends 1221560910 as a <validationErrorCode>. The system looks through the translation keys in the following order searching for the NotEmpty validator for form element LastName:

  • ContactForm.validation.error.LastName.1221560910 (<formDefinitionIdentifier>.validation.error.<elementIdentifier>.<validationErrorCode>)
  • ContactForm.validation.error.1221560910 (<formDefinitionIdentifier>.validation.error.<validationErrorCode>)
  • validation.error.LastName.1221560910 (validation.error.<elementIdentifier>.<validationErrorCode>)
  • validation.error.1221560910 (validation.error.<validationErrorCode>)

As mentioned above, if no translation key is available, a default Extbase framework message is displayed.

Translation of finisher options 

The translation of finisher options is similar to the translation of form definitions above. The same translation files can be used. If the look-up process does not find a key in the provided translation files, the property value will be displayed unmodified.

The same as for form definitions, the translation keys are put together based on a specific pattern. There is also a fallback chain.

The following translation scenarios are possible:

  • translation of finisher options for a specific finisher (finisherIdentifier) of a specific form (formDefinitionIdentifier below)
  • translation of finisher options for a specific finisher (finisherIdentifier) of various forms

The look-up process searches for translation keys in all the translation files based on the following order (the same order as the translation scenarios above):

  • <formDefinitionIdentifier>.finisher.<finisherIdentifier>.<optionName>
  • finisher.<finisherIdentifier>.<optionName>

The translation order is as follows:

  1. Default value from form definition
  2. Overridden value from a FlexForm (if any)
  3. Localized value provided by translation files (if any)

The translation.propertiesExcludedFromTranslation option skips the third step so that the translation resolves to a FlexForm value if one exists. For an example see Skip translation of overridden form finisher options.

Example Form Definition with Finisher 

identifier: ContactForm
type: Form
prototypeName: standard
label: 'Contact us'

finishers:
  -
    identifier: Confirmation
    options:
      message: 'Thank you for your inquiry.'

renderables:
  ...
Copied!

The look-up process searches for the following translation keys for the 'Confirmation' finisher message option:

  • ContactForm.finisher.Confirmation.message (<formDefinitionIdentifier>.finisher.<finisherIdentifier>.<optionName>)
  • finisher.Confirmation.message (finisher.<finisherIdentifier>.<optionName>)

If no translation key exists, the message 'Thank you for your inquiry.' will be displayed.

Form element translation arguments 

Form element property translations and finisher option translations can have placeholders to output translation arguments. Translations can be enriched with variable values by passing arguments to form element properties. This feature was introduced in forge#81363.

Form element properties 

In the YAML form configuration you can add simple literal values:

renderables:
  - identifier: field-with-translation-arguments
    type: Checkbox
    label: This is a %s feature
    renderingOptions:
      translation:
        translationFiles:
          10: path/to/locallang.xlf
        arguments:
          label:
            - useful
Copied!

This will produce the label: This is a useful feature.

Alternatively, you can use formDefinitionOverrides in TypoScript to set translation arguments. One use case is a checkbox for user confirmation which links to further information. Here it makes sense to use YAML hashes (key value pairs) instead of YAML lists so that sections have keys. This simplifies references in TypoScript since named keys are easy to read and can easily be reordered. With lists and numeric keys the TypoScript setup would also need to be updated in this case.

In the following form configuration example the list of renderables has been replaced with a hash of renderables, and the field field-with-translation-arguments now has a named key fieldWithTranslationArguments. This key can be anything as long as it is unique at its level in the YAML - here just the identifier in another form:

renderables:
  fieldWithTranslationArguments:
    identifier: field-with-translation-arguments
    type: Checkbox
    label: I agree to the <a href="%s">terms and conditions</a>
    renderingOptions:
      translation:
        translationFiles:
          10: path/to/locallang.xlf
Copied!

If the label contains HTML markup - like in the above example - it must be wrapped in CDATA tags in the path/to/locallang.xlf translation file, to prevent analysis of character data by the parser. Also, the label should be rendered using the <f:format.raw> ViewHelper in fluid templates, to prevent escaping of HTML tags:

<trans-unit id="<form-id>.element.field-with-translation-arguments.properties.label">
    <source><![CDATA[I agree to the <a href="%s">terms and conditions</a>]]></source>
</trans-unit>
Copied!

The TypoScript below can use the fieldWithTranslationArguments key to refer to the field and adds a page URL as a translation argument for the link in the label:

plugin.tx_form {
  settings {
    formDefinitionOverrides {
      <form-id> {
        renderables {
          0 {
            # Page
              renderables {
                fieldWithTranslationArguments {
                  renderingOptions {
                    translation {
                      arguments {
                        label {
                          0 = TEXT
                          0.typolink {
                            # Terms and conditions page, could be
                            # set also via TypoScript constants
                            parameter = 42
                            returnLast = url
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
   }
}
Copied!

The Page element of the form definition is not registered with a named key so a numeric key 0 is used which, as mentioned above, is prone to errors when more pages are added or reordered.

Finishers 

The same mechanism (YAML, YAML + TypoScript) works for finisher options:

finishers:
  finisherWithTranslationArguments:
    identifier: EmailToReceiver
    options:
      subject: My %s subject
      recipients:
        your.company@example.com: 'Your Company name'
        ceo@example.com: 'CEO'
      senderAddress: bar@example.org
      translation:
        translationFiles:
          10: path/to/locallang.xlf
        arguments:
          subject:
            - awesome
Copied!

This will produce My awesome subject.

Basic code components 

Basic code components

Basic code components

TYPO3\CMS\Form\Domain\Model\FormDefinition 

The class \TYPO3\CMS\Form\Domain\Model\FormDefinition encapsulates a complete form definition, with all of its

  • pages,
  • form elements,
  • validation rules, and
  • finishers which are executed when the form is submitted.

The FormDefinition domain model is not modified when the form is executed.

The anatomy of a form 

A FormDefinition domain model consists of multiple Page objects. When a form is displayed, only one Page is visible at a time. However, you can navigate back and forth between the pages. A Page consists of multiple FormElements which represent input fields, textareas, checkboxes, etc, on a page. The FormDefinition domain model, Page and FormElement objects have identifiers which must be unique for each <formElementTypeIdentifier>, i.e. the FormDefinition domain model and a FormElement object may have the same identifier but two FormElement objects cannot have the same identifier.

Example 

You can create a FormDefinition domain model by calling the API methods on it, or you can use a FormFactory to build the form from a different representation format such as YAML. The example below calls API methods to add a page to a FormDefinition and then to add an element to the page:

$formDefinition = GeneralUtility::makeInstance(FormDefinition::class, 'myForm');

$page1 = GeneralUtility::makeInstance(Page::class, 'page1');
$formDefinition->addPage($page);

// second argument is the <formElementTypeIdentifier> of the form element
$element1 = GeneralUtility::makeInstance(GenericFormElement::class, 'title', 'Text');
$page1->addElement($element1);
Copied!

Creating a form using abstract form element types 

You can use the TYPO3\CMS\Form\Domain\Model\FormDefinition::addPage() and TYPO3\CMS\Form\Domain\Model\FormElements\Page::addElement() methods as above and create the Page and FormElement objects manually, but it is often better to use the corresponding create methods ( TYPO3\CMS\Form\Domain\Model\FormDefinition::createPage() and TYPO3\CMS\Form\Domain\Model\FormElements\Page::createElement()). You only need to pass them an abstract <formElementTypeIdentifier> such as Text or Page and EXT:form will resolve the classname and set default values.

The simple example shown above can then be rewritten as follows:

// we will come back to this later on
$prototypeConfiguration = [];

$formDefinition = GeneralUtility::makeInstance(FormDefinition::class, 'myForm', $prototypeConfiguration);
$page1 = $formDefinition->createPage('page1');
$element1 = $page1->addElement('title', 'Text');
Copied!

You might wonder how the system knows that the element Text is implemented with a GenericFormElement. This is configured in the $prototypeConfiguration. To make the example from above actually work, we need to add some meaningful values to $prototypeConfiguration:

$prototypeConfiguration = [
    'formElementsDefinition' => [
        'Page' => [
            'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\Page'
        ],
        'Text' => [
            'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement'
        ],
    ],
];
Copied!

For each abstract <formElementTypeIdentifier>, we have to add some configuration. In the snippet above, we only define the implementation class name. Apart from that, it is always possible to set default values for all configuration options of such elements, as the following example shows:

$prototypeConfiguration = [
    'formElementsDefinition' => [
        'Page' => [
            'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\Page',
            'label' => 'This is the label of the page if nothing else is specified'
        ],
        'Text' => [
            'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement',
            'label' = >'Default Label',
            'defaultValue' => 'Default form element value',
            'properties' => [
                'placeholder' => 'Text that is shown if element is empty'
            ],
        ],
    ],
];
Copied!

Using pre-configured $prototypeConfiguration 

Often, it does not make sense to manually create the $prototypeConfiguration array. Bigger parts of this array are pre-configured in the extensions's YAML settings. The \TYPO3\CMS\Form\Domain\Configuration\ConfigurationService contains helper methods which return the ready-to-use :php$prototypeConfiguration.

Rendering a FormDefinition 

To trigger the rendering of a FormDefinition domain model, the current \TYPO3\CMS\Extbase\Mvc\Web\Request needs to be bound to the FormDefinition. This binding results in a \TYPO3\CMS\Form\Domain\Runtime\FormRuntime object which contains the Runtime State of the form. Among other things, this object includes the currently inserted values:

// $currentRequest needs to be available.
// Inside a controller, you would use $this->request
$form = $formDefinition->bind($currentRequest);
// now, you can use the $form object to get information about the currently entered values, etc.
Copied!

TYPO3\CMS\Form\Domain\Runtime\FormRuntime 

This class implements the runtime logic of a form, i.e. the class

  • decides which page is currently shown,
  • determines the current values of the form
  • triggers validation and property mappings.

You generally receive an instance of this class by calling TYPO3\CMS\Form\Domain\Model\FormDefinition::bind().

Rendering a form 

Rendering a form is easy. Just call render() on the FormRuntime:

$form = $formDefinition->bind($request);
$renderedForm = $form->render();
Copied!

Accessing form values 

In order to get the values the user has entered into the form, you can access the FormRuntime object like an array. If a form element with the identifier firstName exists, you can use $form['firstName'] to retrieve its current value. You can set values the same way.

Rendering internals 

The FormRuntime inquires the FormDefinition domain model regarding the configured renderer ( TYPO3\CMS\Form\Domain\Model\FormDefinition::getRendererClassName()) and then triggers render() on this Renderer.

This allows you to declaratively define how a form should be rendered.

prototypes:
  standard:
    formElementsDefinition:
      Form:
        rendererClassName: 'TYPO3\CMS\Form\Domain\Renderer\FluidFormRenderer'
Copied!

TYPO3\CMS\Form\Domain\Renderer\FluidFormRenderer 

This class is a \TYPO3\CMS\Form\Domain\Renderer\RendererInterface implementation which used to render a FormDefinition domain model. It is the default EXT:form renderer.

Learn more about the FluidFormRenderer Options.

Custom form element implementations 

EXT:form ships a decent amount of hooks which are available at crucial points of the life cycle of a FormElement. Most of the time, own implementations are therefore unnecessary. An own form element can be defined by:

  • writing some configuration, and
  • utilizing the standard implementation of \TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement .
prototypes:
  standard:
    formElementsDefinition:
      CustomFormElementIdentifier:
        implementationClassName: 'TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement'
Copied!

With the provided hooks, this FormElement can now be manipulated.

If you insist on your own implementation, the abstract class \TYPO3\CMS\Form\Domain\Model\FormElements\AbstractFormElement offers a perfect entry point. In addition, we recommend checking-out \TYPO3\CMS\Form\Domain\Model\Renderable\AbstractRenderable . All of your own form element implementations must be programmed to the interface \TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface . It is a good idea to derive your implementation from \TYPO3\CMS\Form\Domain\Model\FormElements\AbstractFormElement .

"render" viewHelper 

The RenderViewHelper is the actual starting point for form rendering and not the typical Extbase Controller as you may know it.

For more technical insights read more about the viewHelper's arguments.

Render through FLUIDTEMPLATE (without controller) 

tt_content.custom_content_element = COA_INT
tt_content.custom_content_element {
  20 = FLUIDTEMPLATE
  20 {
    file = EXT:my_site_package/Resources/Private/Templates/CustomContentElement.html
    settings {
      persistenceIdentifier = EXT:my_site_package/Resources/Private/Forms/MyForm.yaml
    }
    extbase.pluginName = Formframework
    extbase.controllerExtensionName = Form
    extbase.controllerName = FormFrontend
    extbase.controllerActionName = perform
  }
}
Copied!

my_site_package/Resources/Private/Templates/CustomContentElement.html:

<formvh:render persistenceIdentifier="{settings.persistenceIdentifier}" />
Copied!

Render within your own Extbase extension 

It is straight forward. Use the RenderViewHelper like this and you are done:

<formvh:render persistenceIdentifier="EXT:my_site_package/Resources/Private/Forms/MyForm.yaml"/>
Copied!

Point the property controllerAction to the desired action name and provide values for the other parameters displayed below (you might need those).

type: Form
identifier: 'example-form'
label: 'TYPO3 is cool'
prototypeName: standard
renderingOptions:
  controllerAction: perform
  addQueryString: false
  argumentsToBeExcludedFromQueryString: []
  additionalParams: []

renderables:
  ...
Copied!

Build forms programmatically 

To learn more about this topic, head to the chapter 'Build forms programmatically' which is part of the API reference section.

Runtime manipulation 

Hooks 

EXT:form implements a decent amount of hooks that allow the manipulation of your forms during runtime. In this way, it is possible to, for example,

  • ... prefill form elements with values from your database,
  • ... skip a whole page based on the value of a certain form element,
  • ... mark a form element as mandatory depending of the chosen value of another form element.

Please check out the 'API reference section' for more details.

TypoScript overrides 

Each and every form definition can be overridden via TypoScript if the FormFrontendController of EXT:form is used to render the form. Normally, this is the case if the form has been added to the page using the form plugin or when rendering the form via FLUIDTEMPLATE.

The overriding of settings with TypoScript's help takes place after the custom finisher settings of the form plugin have been loaded. In this way, you are able to manipulate the form definition for a single page. In doing so, the altered form definition is passed to the RenderViewHelper which then generates the form programmatically. At this point, you can still change the form elements using the above-mentioned concept of hooks.

plugin.tx_form {
  settings {
    formDefinitionOverrides {
      <formDefinitionIdentifier> {
        renderables {
          0 {
            renderables {
              0 {
                label = TEXT
                label.value = Overridden label
              }
            }
          }
        }
      }
    }
  }
}
Copied!