TYPO3 Setup
The TYPO3 integration layer wraps the generic FormLayer library with TYPO3-specific defaults. A single initTypo3Forms() call handles everything.
Basic Setup
Section titled “Basic Setup”Frontend
Section titled “Frontend”import { initTypo3Forms } from 'formlayer/typo3';import { registerComboboxPlugin } from 'formlayer-plugin-combobox';import { registerClientVariantsPlugin } from 'formlayer-plugin-client-variants';import { registerDatepickerPlugin } from 'formlayer-plugin-datepicker';import { registerTypo3AltchaPlugin } from 'formlayer-plugin-altcha/typo3';
registerComboboxPlugin();registerClientVariantsPlugin();registerDatepickerPlugin();registerTypo3AltchaPlugin();
const api = initTypo3Forms();Backend
Section titled “Backend”Enable Use AJAX submission on the form content element in the TYPO3 backend (FlexForm checkbox). This activates the custom Fluid templates and hidden fields required for AJAX.
See TYPO3 Backend (PHP) for middleware, templates, and server-side configuration.
What initTypo3Forms Does
Section titled “What initTypo3Forms Does”- Registers all 12 built-in validators
- Creates an AJAX submit handler (
createTypo3Submit) that sendsX-Form-Ajax: 1 - Provides
remountandunmountfor multistep form lifecycle - Scans for
form[id]elements and initializes each one - Returns a
Typo3FormsApiobject for cleanup
Plugins (combobox, client-variants, datepicker, altcha) are separate npm packages — register them before calling initTypo3Forms(). See Plugins.
Configuration Options
Section titled “Configuration Options”const api = initTypo3Forms({ disableDefaultValidators: false,
additionalValidators: [ { type: 'PhoneNumber', validate(value, options) { if (!value) return { valid: true, message: '' }; const valid = /^\+?\d[\d\s\-()]{6,}$/.test(value); return { valid, message: valid ? '' : (options['message'] as string) ?? 'Invalid phone number' }; }, }, ],
additionalFieldTypes: { 'color-picker': () => import('./fields/color-picker'), },
additionalFormPlugins: [ () => import('./plugins/form-analytics'), ],
// Override the submit function entirely // onSubmit: myCustomSubmitFn,
formSelector: 'form[id]', fieldSelector: '[data-form-field]',
hooks: { onFormRegistered(api) { console.log(`Form "${api.id}" ready`); }, onBeforeSubmit(ctx) { return false; // cancel submit }, onAfterSubmit(response, formEl) { /* ... */ }, onValidationError(errors, formEl) { /* ... */ }, onStepChange(page, formEl) { /* new form element after remount */ }, onFormFinished(response, formEl) { /* ... */ }, onSubmitError(error, formEl) { /* ... */ }, },});Lifecycle Hooks
Section titled “Lifecycle Hooks”| Hook | When | Can cancel? |
|---|---|---|
onFormRegistered | Form controller created (also after remount) | No |
onBeforeSubmit | Before AJAX request is sent | Yes (return false) |
onAfterSubmit | After response JSON is parsed | No |
onValidationError | Server returned validation errors | No |
onStepChange | Multistep form advanced; receives remounted form element | No |
onFormFinished | Server indicated form is complete | No |
onSubmitError | Network failure, non-OK status, or invalid JSON | No |
AJAX Submit Flow
Section titled “AJAX Submit Flow”User clicks Submit → Client validation (FormController) → data-loading on submit button (default) → onBeforeSubmit hook (return false to cancel) → POST with X-Form-Ajax: 1 header → AjaxFormSubmitMiddleware (PHP) returns JSON → onAfterSubmit hook → If !valid: update __state + apply field errors + onValidationError → If finished: onFormFinished → unmount → redirect or replace with message → If multistep: remount (outerHTML) + onStepChangeForm Lifecycle: Remount and Unmount
Section titled “Form Lifecycle: Remount and Unmount”TYPO3 multistep forms use registry-level lifecycle management instead of generic FormController methods:
| Action | When | What happens |
|---|---|---|
| remount | Valid step with html in response | Unregister old form → replace outerHTML → register new form |
| unmount | Form finished (no redirect) | Unregister form → replace with finisher message HTML |
This ensures plugins, event listeners, and field controllers are properly destroyed before DOM replacement.
Generic finish() from FormSubmitContext is not used by the TYPO3 submit handler. Cleanup goes through unmount instead.
Expected Server Response
Section titled “Expected Server Response”interface Typo3AjaxFormResponse { valid: boolean; errors: Record<string, string[]>; page: { current: number; total: number }; finished: boolean; redirect: string | null; message: string | null; // finisher HTML state: string; // HMAC-protected FormState html?: string | null; // full <form> HTML for next step}Loading State
Section titled “Loading State”Submit buttons get data-loading automatically via the generic FormController. Style with CSS:
.t3-form button[data-loading] { opacity: 0.6; pointer-events: none;}See Loading State for customization.
Cleanup
Section titled “Cleanup”const api = initTypo3Forms();api.destroy(); // unregisters all forms, removes listenersAfter destroy(), you can call initTypo3Forms() again.
Using the Registry Directly
Section titled “Using the Registry Directly”const api = initTypo3Forms();
const form = api.registry.get('my-form-id');form?.on('form:loading', (detail) => { console.log('Submitting:', detail.state.isSubmitting);});
// Manual registration (uses same submitFn as init)const lateForm = document.getElementById('dynamic-form') as HTMLFormElement;api.registry.register(lateForm, createTypo3Submit());Related Guides
Section titled “Related Guides”- Multistep Forms — step transitions, summary steps, remounting
- TYPO3 Backend (PHP) — middleware, FlexForm, Fluid templates
- Loading State — submit button loading UI