Building and rendering forms
This chapter explains how EXT:form renders forms in the frontend and how developers can build forms programmatically or customize the rendering pipeline.
For the complete PHP API of every class mentioned here, see EXT:form API on api.typo3.org.
Template resolution (FluidFormRenderer)
The
Fluid resolves
Fluid templates, layouts and partials through rendering options defined in the
prototype configuration. All options are read from
\TYPO3\.
templateRootPaths
Defines one or more paths to Fluid templates. Paths are searched in reverse order (bottom to top); the first match wins.
Only the root form element (type
Form) must be a template file.
All child elements are resolved as partials.
prototypes:
standard:
formElementsDefinition:
Form:
renderingOptions:
templateRootPaths:
10: 'EXT:form/Resources/Private/Frontend/Templates/'
20: 'EXT:my_sitepackage/Resources/Private/Forms/Frontend/Templates/'
With the default type
Form the renderer expects a file named
Form. inside the first matching path.
layoutRootPaths
Defines one or more paths to Fluid layouts, searched in reverse order.
partialRootPaths
Defines one or more paths to Fluid partials, searched in reverse order.
Within these paths the renderer looks for a file named after the
form element type (e.g. Text. for a
Text element).
Use templateName
to override this convention.
templateName
By default the element type is used as the partial file name
(e.g. type
Text → Text.).
Set
template to use a different file instead:
The element of type
Foo now renders using Text..
The render ViewHelper
Use
<formvh: in a Fluid template to render a form.
The ViewHelper accepts the following arguments:
persistenceIdentifier
Path to a YAML form definition. This is the most common way to render a form:
overrideConfiguration
A configuration array that is merged on top of the loaded form
definition (or passed directly to the factory when no
persistence is given).
This allows adjusting a form per usage without duplicating the YAML file.
factoryClass
A fully qualified class name implementing
Form.
Defaults to
Array.
Set a custom factory to build forms programmatically.
prototypeName
Name of the prototype the factory should use (e.g.
standard).
If omitted the framework looks for the prototype name inside the form
definition; if none is found,
standard is used.
Building forms programmatically
Instead of writing YAML, you can create a form entirely in PHP by
implementing a custom
Form.
-
Create a FormFactory
Extend
Abstractand implementForm Factory build. Use() Formto add pages,Definition:: create Page () Page::to add elements, andcreate Element () Formto attach finishers.Definition:: create Finisher () EXT:my_sitepackage/Classes/Domain/Factory/CustomFormFactory.php<?php declare(strict_types=1); namespace Vendor\MySitePackage\Domain\Factory; use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Form\Domain\Configuration\ConfigurationService; use TYPO3\CMS\Form\Domain\Factory\AbstractFormFactory; use TYPO3\CMS\Form\Domain\Model\FormDefinition; use TYPO3\CMS\Form\Domain\Model\FormElements\AbstractFormElement; final class CustomFormFactory extends AbstractFormFactory { public function build( array $configuration, ?string $prototypeName = null, ?ServerRequestInterface $request = null, ): FormDefinition { $prototypeName ??= 'standard'; $configurationService = GeneralUtility::makeInstance( ConfigurationService::class, ); $prototypeConfiguration = $configurationService->getPrototypeConfiguration( $prototypeName, ); $form = GeneralUtility::makeInstance( FormDefinition::class, 'ContactForm', $prototypeConfiguration, ); $form->setRenderingOption('controllerAction', 'index'); // Page 1 – personal data $page1 = $form->createPage('page1'); /** @var AbstractFormElement $name */ $name = $page1->createElement('name', 'Text'); $name->setLabel('Name'); $name->createValidator('NotEmpty'); /** @var AbstractFormElement $email */ $email = $page1->createElement('email', 'Text'); $email->setLabel('Email'); // Page 2 – message $page2 = $form->createPage('page2'); /** @var AbstractFormElement $message */ $message = $page2->createElement('message', 'Textarea'); $message->setLabel('Message'); $message->createValidator('StringLength', ['minimum' => 5, 'maximum' => 500]); // Radio buttons /** @var AbstractFormElement $subject */ $subject = $page2->createElement('subject', 'RadioButton'); $subject->setProperty('options', [ 'general' => 'General inquiry', 'support' => 'Support request', ]); $subject->setLabel('Subject'); // Finisher – send email $form->createFinisher('EmailToSender', [ 'subject' => 'Contact form submission', 'recipients' => [ 'info@example.com' => 'My Company', ], 'senderAddress' => 'noreply@example.com', ]); $this->triggerFormBuildingFinished($form); return $form; } } -
Render the form
Reference your factory in a Fluid template:
EXT:my_sitepackage/Resources/Private/Templates/ContactPage.html
Key classes and their responsibilities
The following table lists the most important classes you work with when building or manipulating forms programmatically. Use your IDE's autocompletion or the API documentation for the full method reference.
| Class | Purpose |
|---|---|
Form | The complete form model. Create pages (
create),
attach finishers (
create), look up elements
(
get), and bind to a request
(
bind). |
Form | A bound form instance (created by
Form).
Provides access to the current page, submitted values
(
get), and the request/response objects.
This is the object available inside finishers and event listeners. |
Page | One page of a multi-step form. Add elements with
create, reorder them with
move
/
move. |
Section | A grouping element inside a page. Same API as
Page for
managing child elements. |
Abstract | Base class of all concrete elements. Most element types use
Generic;
specialized subclasses include
Date and
File.
Set properties (
set), add validators
(
create), define default values
(
set). |
Configuration | Reads the merged prototype configuration.
Call
get to obtain the
full array for a prototype. |
Initializing elements at runtime
Override
initialize in a custom form element class to
populate data (e.g. from a database) when the element is added to the form.
At that point the prototype defaults have already been applied; properties
from the YAML definition are applied afterwards.
Tip
If you only need to initialize an element without writing a full custom
class, listen to the
Before PSR-14
event instead. See PSR-14 Events.
Working with finishers
Custom finishers extend
Abstract
and place their logic in
execute. The base class provides:
parseOption (string $option Name) - Resolves a finisher option, applying form-element variable replacements and TypoScript-style option overrides. Always prefer this over direct array access.
The
Finisher passed to
execute gives access to:
getForm Runtime () - The
Formfor the current submission.Runtime getForm Values () - All submitted values (after validation and property mapping).
getFinisher Variable Provider () -
A key/value store to share data between finishers within the same request. The returned
Finisheroffers:Variable Provider add(string $finisher Identifier, string $key, mixed $value) - Store a value under a finisher-specific namespace.
get(string $finisher Identifier, string $key, mixed $default = null) - Retrieve a previously stored value (returns
$defaultif not set). exists(string $finisher Identifier, string $key) - Check whether a value has been stored.
remove(string $finisher Identifier, string $key) - Remove a stored value.
cancel() - Stops execution of any remaining finishers.
Runtime manipulation
EXT:form dispatches PSR-14 events at every important step of the rendering lifecycle. Use them to modify the form, redirect page flow, or adjust submitted values – without subclassing framework internals.
See also