---
title: "JavaScript events"
manual: "Form"
version: "14.3"
permalink: "https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents@14.3"
source: "D/FormEditor/JavaScriptEvents/Index.rst"
rendered: "2026-09-19T11:59:02+00:00"
---

# JavaScript events {#apireference-formeditor-jsevents}

The form editor uses a **publish/subscribe bus** for all cross-component
communication. Any custom JavaScript module can subscribe to these events
to extend or react to editor behaviour without patching core files.

> [!NOTE]
> The module system is **ES modules** (ESM). The legacy AMD
> `define([…], function() {})` pattern from TYPO3 v11 and earlier is
> no longer supported. Custom modules must use `export function bootstrap(formEditorApp)`.
> See [Registering a custom JavaScript module](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-custom-modules@14.3).

-   [Publish / subscribe basics](https://docs.typo3.org/permalink/typo3/cms-form:publish-subscribe-basics@14.3)
-   [Event quick-reference](https://docs.typo3.org/permalink/typo3/cms-form:event-quick-reference@14.3)
-   [Event reference](https://docs.typo3.org/permalink/typo3/cms-form:event-reference@14.3)

## Publish / subscribe basics {#apireference-formeditor-jsevents-pubsub}

**Subscribe to an event**

```javascript
export function bootstrap(formEditorApp) {
    const ps = formEditorApp.getPublisherSubscriber();

    // Subscribe – returns a token for later unsubscription
    const token = ps.subscribe('view/ready', (topic, args) => {
        // args is a typed tuple matching the event signature
    });

    // Unsubscribe
    ps.unsubscribe(token);
}

```

**Publish a custom event from within your module**

```javascript
export function bootstrap(formEditorApp) {
    formEditorApp.getPublisherSubscriber().publish('my/custom/event', ['arg1', 'arg2']);
}

```

> [!NOTE]
> The order in which subscribers receive an event is not guaranteed.
> Subscribers cannot pass data to each other. All event handlers must
> be designed without assumptions about execution order.

## Event quick-reference {#apireference-formeditor-jsevents-overview}

### Lifecycle {#apireference-formeditor-jsevents-overview-lifecycle}

| Event | When it fires |
| --- | --- |
| [view/ready](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-ready@14.3) | All modules loaded; editor is fully initialised. |

### Ajax / data transfer {#apireference-formeditor-jsevents-overview-ajax}

| Event | When it fires |
| --- | --- |
| [core/ajax/saveFormDefinition/success](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-core-ajax-savesuccess@14.3) | Form definition saved successfully. |
| [core/ajax/saveFormDefinition/error](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-core-ajax-saveerror@14.3) | Server returned an error while saving. |
| [core/ajax/renderFormDefinitionPage/success](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-core-ajax-rendersuccess@14.3) | Preview HTML for the current page returned successfully. |
| [core/ajax/error](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-core-ajax-error@14.3) | Any Ajax request (save or preview render) failed. |

### Application state {#apireference-formeditor-jsevents-overview-state}

| Event | When it fires |
| --- | --- |
| [core/applicationState/add](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-core-applicationstate-add@14.3) | Undo/redo stack was updated. |
| [core/currentlySelectedFormElementChanged](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-core-currentlyselectedformelementchanged@14.3) | The currently selected form element changed. |
| [core/formElement/somePropertyChanged](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-core-formelement-somepropertychanged@14.3) | A property was written to a FormElement model via `set()`. |

### Form element lifecycle {#apireference-formeditor-jsevents-overview-formelement}

| Event | When it fires |
| --- | --- |
| [view/formElement/inserted](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-formelement-inserted@14.3) | A new form element was added to the tree. |
| [view/formElement/moved](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-formelement-moved@14.3) | A form element was moved within the tree. |
| [view/formElement/removed](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-formelement-removed@14.3) | A form element was deleted. |

### Collection elements (validators / finishers) {#apireference-formeditor-jsevents-overview-collection}

| Event | When it fires |
| --- | --- |
| [view/collectionElement/new/added](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-collectionelement-new-added@14.3) | A validator or finisher was added. |
| [view/collectionElement/moved](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-collectionelement-moved@14.3) | A validator or finisher was reordered. |
| [view/collectionElement/removed](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-collectionelement-removed@14.3) | A validator or finisher was removed. |

### Insert element / page dialogs {#apireference-formeditor-jsevents-overview-insert}

| Event | When it fires |
| --- | --- |
| [view/insertElements/perform/before](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-insertelements-perform-before@14.3) | Insert new element *before* the selected one. |
| [view/insertElements/perform/after](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-insertelements-perform-after@14.3) | Insert new element *after* the selected one. |
| [view/insertElements/perform/inside](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-insertelements-perform-inside@14.3) | Insert new element *inside* the selected composite. |
| [view/insertElements/perform/bottom](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-insertelements-perform-bottom@14.3) | Insert new element at the end of the current page. |
| [view/insertPages/perform](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-insertpages-perform@14.3) | Insert a new page after the current one. |

### Header buttons {#apireference-formeditor-jsevents-overview-header}

| Event | When it fires |
| --- | --- |
| [view/header/button/save/clicked](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-header-save@14.3) | "Save" button clicked. |
| [view/header/button/close/clicked](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-header-close@14.3) | "Close" button clicked (with unsaved changes guard). |
| [view/header/button/newPage/clicked](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-header-newpage@14.3) | "New page" button clicked. |
| [view/header/formSettings/clicked](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-header-formsettings@14.3) | "Form settings" button clicked. |
| [view/undoButton/clicked](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-undobutton@14.3) | Undo button clicked. |
| [view/redoButton/clicked](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-redobutton@14.3) | Redo button clicked. |
| [view/viewModeButton/abstract/clicked](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-viewmode-abstract@14.3) | "Abstract view" toggle clicked. |
| [view/viewModeButton/preview/clicked](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-viewmode-preview@14.3) | "Preview" toggle clicked. |
| [view/paginationNext/clicked](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-pagination-next@14.3) | "Next page" pagination button clicked. |
| [view/paginationPrevious/clicked](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-pagination-previous@14.3) | "Previous page" pagination button clicked. |

### Stage {#apireference-formeditor-jsevents-overview-stage}

| Event | When it fires |
| --- | --- |
| [view/stage/abstract/render/template/perform](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-stage-abstract-render-template-perform@14.3) | **Main extension point.** Stage renders a form element that has a `formEditorPartials` entry. |
| [view/stage/abstract/render/preProcess](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-stage-abstract-render-preprocess@14.3) | Before the abstract stage area is rendered. |
| [view/stage/abstract/render/postProcess](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-stage-abstract-render-postprocess@14.3) | After the abstract stage area was rendered. |
| [view/stage/preview/render/postProcess](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-stage-preview-render-postprocess@14.3) | After the preview stage area was rendered. |
| [view/stage/element/clicked](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-stage-element-clicked@14.3) | A form element in the stage was clicked. |
| [view/stage/panel/clicked](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-stage-panel-clicked@14.3) | The stage panel background was clicked. |
| [view/stage/abstract/button/newElement/clicked](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-stage-abstract-button-newelement@14.3) | "Add element" button at the bottom of the stage clicked. |
| [view/stage/abstract/elementToolbar/button/newElement/clicked](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-stage-abstract-toolbar-newelement@14.3) | Toolbar "add element" / split button on an element clicked. |
| [view/stage/abstract/dnd/start](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-stage-dnd-start@14.3) | Drag started in the stage. |
| [view/stage/abstract/dnd/change](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-stage-dnd-change@14.3) | Drag position changed in the stage. |
| [view/stage/abstract/dnd/update](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-stage-dnd-update@14.3) | Drag ended, model position updated. |
| [view/stage/abstract/dnd/stop](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-stage-dnd-stop@14.3) | Drag operation finished. |

### Inspector {#apireference-formeditor-jsevents-overview-inspector}

| Event | When it fires |
| --- | --- |
| [view/inspector/editor/insert/perform](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-inspector-editor-insert@14.3) | **Extension point for custom inspector editors.** |
| [view/inspector/collectionElement/new/selected](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-inspector-collection-new-selected@14.3) | A new validator/finisher was chosen in the select box. |
| [view/inspector/collectionElement/existing/selected](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-inspector-collection-existing-selected@14.3) | An existing validator/finisher section was expanded. |
| [view/inspector/collectionElements/dnd/update](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-inspector-collection-dnd-update@14.3) | A validator/finisher was reordered via drag-and-drop. |
| [view/inspector/removeCollectionElement/perform](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-inspector-removecollectionelement@14.3) | Remove a validator/finisher (from RequiredValidatorEditor checkbox). |

### Structure tree {#apireference-formeditor-jsevents-overview-structure}

| Event | When it fires |
| --- | --- |
| [view/structure/root/selected](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-structure-root-selected@14.3) | Root element in the tree was clicked. |
| [view/structure/button/newPage/clicked](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-structure-button-newpage@14.3) | "New page" button in the tree panel clicked. |
| [view/structure/renew/postProcess](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-structure-renew-postprocess@14.3) | Tree was re-rendered. |
| [view/tree/node/clicked](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-tree-node-clicked@14.3) | A tree node was clicked. |
| [view/tree/node/changed](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-tree-node-changed@14.3) | A tree node label was edited inline. |
| [view/tree/render/listItemAdded](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-tree-listitem-added@14.3) | Reserved – not yet published by core. (A list item was added to the tree.) |
| [view/tree/dnd/change](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-tree-dnd-change@14.3) | Drag position changed in the tree. |
| [view/tree/dnd/update](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-tree-dnd-update@14.3) | Drag ended, model position updated. |
| [view/tree/dnd/stop](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-tree-dnd-stop@14.3) | Drag operation finished. |

### Dialogs (modals) {#apireference-formeditor-jsevents-overview-modals}

| Event | When it fires |
| --- | --- |
| [view/modal/close/perform](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-modal-close@14.3) | User confirmed closing the editor with unsaved changes. |
| [view/modal/removeFormElement/perform](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-modal-removeformelement@14.3) | User confirmed deleting a form element. |
| [view/modal/removeCollectionElement/perform](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-modal-removecollectionelement@14.3) | User confirmed removing a validator/finisher. |
| [view/modal/validationErrors/element/clicked](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-jsevents-view-modal-validationerrors-clicked@14.3) | A form element was clicked in the validation-error dialog. |

## Event reference {#apireference-formeditor-jsevents-reference}

### view/ready {#apireference-formeditor-jsevents-view-ready}

Published once all additional view-model modules registered via
`dynamicJavaScriptModules.additionalViewModelModules` have
bootstrapped. EXT:form uses this event to remove the loading indicator
and finish editor initialisation. This is the earliest safe point to
interact with the fully wired editor.

-   *Arguments:* none

```javascript
export function bootstrap(formEditorApp) {
    formEditorApp.getPublisherSubscriber().subscribe('view/ready', () => {
        // Safe to call any formEditorApp API here.
    });
}

```

### core/ajax/saveFormDefinition/success {#apireference-formeditor-jsevents-core-ajax-savesuccess}

Published after the form definition was saved successfully. EXT:form
shows a success flash message, updates the in-memory form definition
and re-renders all components.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `{ status: string, formDefinition: object }` | Response payload; `formDefinition` is the saved definition. |

### core/ajax/saveFormDefinition/error {#apireference-formeditor-jsevents-core-ajax-saveerror}

Published when the save Ajax request returns a server-side error.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `{ status: string, message: string, code: number }` | Error details from the server. |

### core/ajax/renderFormDefinitionPage/success {#apireference-formeditor-jsevents-core-ajax-rendersuccess}

Published after the preview Ajax request returns successfully. EXT:form
uses this to display the rendered form HTML in the preview stage.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | Rendered HTML of the current form page. |
| `args[1]` | `number` | Zero-based index of the rendered page. |

### core/ajax/error {#apireference-formeditor-jsevents-core-ajax-error}

Published when any Ajax request (save or preview render) fails at the
HTTP level. EXT:form shows an error flash message and displays the raw
error in the preview area.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | HTTP status text (e.g. `'Internal Server Error'`). |
| `args[1]` | `string` | Raw response body. |

### core/applicationState/add {#apireference-formeditor-jsevents-core-applicationstate-add}

Published every time an action (add / remove / move element or
collection element) is pushed onto the undo/redo stack. EXT:form uses
this to enable or disable the undo/redo buttons.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `ApplicationState` | Snapshot of the application state that was just pushed. |
| `args[1]` | `number` | Current stack pointer position (0-based). |
| `args[2]` | `number` | Total number of entries in the undo/redo stack. |

### core/currentlySelectedFormElementChanged {#apireference-formeditor-jsevents-core-currentlyselectedformelementchanged}

Published at the end of `formEditorApp.setCurrentlySelectedFormElement()`.
All components that need to react to a selection change (inspector, stage,
tree highlight) subscribe to this event.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `FormElement` | The newly selected FormElement model. |

### core/formElement/somePropertyChanged {#apireference-formeditor-jsevents-core-formelement-somepropertychanged}

Published by the FormElement model whenever a property is written via
`set()`. EXT:form uses this to keep the tree labels, stage and
inspector in sync. It is also the mechanism behind
[FormElement.on()](https://docs.typo3.org/permalink/typo3/cms-form:apireference-formeditor-formelementmodel-api-on@14.3).

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | Dot-separated property path that was written. |
| `args[1]` | `unknown` | New value. |
| `args[2]` | `unknown` | Previous value. |
| `args[3]` | `string \| undefined` | `__identifierPath` of the element whose property changed. |

```javascript
export function bootstrap(formEditorApp) {
    formEditorApp.getPublisherSubscriber().subscribe(
        'core/formElement/somePropertyChanged',
        (topic, args) => {
            const [propertyPath, newValue, oldValue, identifierPath] = args;
            if (propertyPath === 'label' && identifierPath?.startsWith('my-form/page-1/')) {
                console.log('Label changed from', oldValue, 'to', newValue);
            }
        },
    );
}

```

### view/formElement/inserted {#apireference-formeditor-jsevents-view-formelement-inserted}

Published after a new form element has been added to the form definition
tree. EXT:form selects the new element and re-renders tree, stage and
inspector.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `FormElement` | The newly inserted FormElement model. |

### view/formElement/moved {#apireference-formeditor-jsevents-view-formelement-moved}

Published after a form element has been moved within the tree. EXT:form
does not add additional behaviour here by default.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `FormElement` | The moved FormElement model. |

### view/formElement/removed {#apireference-formeditor-jsevents-view-formelement-removed}

Published after a form element has been removed. EXT:form selects the
parent element and re-renders tree, stage and inspector.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `FormElement` | The parent FormElement model of the deleted element. |

### view/collectionElement/new/added {#apireference-formeditor-jsevents-view-collectionelement-new-added}

Published after a new validator or finisher has been created and added to
the form definition. EXT:form re-renders the inspector.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | Identifier of the new collection element (e.g. `'NotEmpty'`). |
| `args[1]` | `string` | Collection name: `'validators'` or `'finishers'`. |
| `args[2]` | `FormElement` | The owning form element. |
| `args[3]` | `object` | Full configuration object of the added collection element. |
| `args[4]` | `string` | Identifier of the reference element (inserted before/after). |

### view/collectionElement/moved {#apireference-formeditor-jsevents-view-collectionelement-moved}

Published after a validator or finisher has been reordered. EXT:form
re-renders the inspector.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | Identifier of the moved element. |
| `args[1]` | `string` | Relative position: `'before'` or `'after'`. |
| `args[2]` | `string` | Identifier of the reference element. |
| `args[3]` | `string` | Collection name. |
| `args[4]` | `FormElement` | The owning form element. |

### view/collectionElement/removed {#apireference-formeditor-jsevents-view-collectionelement-removed}

Published after a validator or finisher has been removed from the form
definition. EXT:form re-renders the inspector.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | Identifier of the removed element. |
| `args[1]` | `string` | Collection name. |
| `args[2]` | `FormElement` | The owning form element. |

### view/insertElements/perform/before {#apireference-formeditor-jsevents-view-insertelements-perform-before}

Published when the user selects an element type in the "New element"
dialog after clicking the "Before" toolbar option. EXT:form creates the
new element and moves it *before* the currently selected element.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | Form element type identifier (e.g. `'Text'`). |

### view/insertElements/perform/after {#apireference-formeditor-jsevents-view-insertelements-perform-after}

Published when the user selects an element type after clicking the
"After" toolbar option or the standard toolbar button for non-composite
elements. EXT:form creates the element and moves it *after* the selected
element (as a sibling).

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | Form element type identifier. |

### view/insertElements/perform/inside {#apireference-formeditor-jsevents-view-insertelements-perform-inside}

Published when the user selects an element type after clicking the
"Inside" toolbar option on a composite element (e.g. Fieldset). EXT:form
creates the element as a *child* of the currently selected composite.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | Form element type identifier. |

### view/insertElements/perform/bottom {#apireference-formeditor-jsevents-view-insertelements-perform-bottom}

Published when the user selects an element type after clicking the
"Create new element" button at the very bottom of the stage in abstract
view. EXT:form appends the element as the last child of the current page.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | Form element type identifier. |

### view/insertPages/perform {#apireference-formeditor-jsevents-view-insertpages-perform}

Published when the user selects a page type in the "New page" dialog.
EXT:form creates the page *after* the currently selected page.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | Form element type identifier (typically `'Page'`). |

### view/header/button/save/clicked {#apireference-formeditor-jsevents-view-header-save}

Published when the "Save" button is clicked. EXT:form either opens a
validation-error dialog (if there are errors) or saves the form definition.

-   *Arguments:* none

### view/header/button/close/clicked {#apireference-formeditor-jsevents-view-header-close}

Published when the "Close" button is clicked *and* the form has unsaved
changes. EXT:form opens a confirmation dialog.

-   *Arguments:* none

### view/header/button/newPage/clicked {#apireference-formeditor-jsevents-view-header-newpage}

Published when the "New page" icon in the header is clicked. EXT:form
opens the "New page" dialog.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `'view/insertPages/perform'` | The event to publish once the user picks a page type. |

### view/header/formSettings/clicked {#apireference-formeditor-jsevents-view-header-formsettings}

Published when the "Form settings" button is clicked. EXT:form selects
the root form element and renders its settings in the inspector.

-   *Arguments:* none

### view/undoButton/clicked {#apireference-formeditor-jsevents-view-undobutton}

Published when the undo button is clicked. EXT:form steps back one state
in the undo/redo stack and re-renders all components.

-   *Arguments:* none

### view/redoButton/clicked {#apireference-formeditor-jsevents-view-redobutton}

Published when the redo button is clicked. EXT:form steps forward one
state in the undo/redo stack and re-renders all components.

-   *Arguments:* none

### view/viewModeButton/abstract/clicked {#apireference-formeditor-jsevents-view-viewmode-abstract}

Published when the "Abstract view" toggle in the stage header is clicked.
EXT:form switches to abstract view if not already active.

-   *Arguments:* none

### view/viewModeButton/preview/clicked {#apireference-formeditor-jsevents-view-viewmode-preview}

Published when the "Preview" toggle in the stage header is clicked.
EXT:form switches to preview view if not already active.

-   *Arguments:* none

### view/paginationNext/clicked {#apireference-formeditor-jsevents-view-pagination-next}

Published when the "next page" arrow in the stage header is clicked.
EXT:form advances to the next form page.

-   *Arguments:* none

### view/paginationPrevious/clicked {#apireference-formeditor-jsevents-view-pagination-previous}

Published when the "previous page" arrow in the stage header is clicked.
EXT:form goes back to the previous form page.

-   *Arguments:* none

### view/stage/abstract/render/template/perform {#apireference-formeditor-jsevents-view-stage-abstract-render-template-perform}

**The primary extension point for custom stage rendering.**

Published by the Stage component for each form element that has a
`formEditorPartials` entry in the prototype configuration. Form
elements *without* a `formEditorPartials` entry are rendered
automatically by the
`<typo3-form-form-element-stage-item>` web component — no
subscriber is needed for those.

> [!NOTE]
> For most custom form elements the web-component approach (no
> `formEditorPartials`, no subscriber) is sufficient.
> Use this event only when you need fully custom DOM inside the stage
> that the built-in web component cannot provide.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `FormElement` | The FormElement model being rendered. |
| `args[1]` | `HTMLElement` | Cloned DOM node from the Fluid partial. Populate this via DOM manipulation. |

**Full example — custom element with a dedicated stage partial:**

*Fluid partial* (`EXT:my_extension/Resources/Private/Backend/Partials/FormEditor/Stage/MyCustomElement.html`):

```html
<div class="formeditor-element-body">
    <div data-identifier="elementLabel"></div>
    <div data-identifier="elementSummary"></div>
</div>

```

*Prototype YAML configuration:*

```yaml
prototypes:
  standard:
    formEditor:
      dynamicJavaScriptModules:
        additionalViewModelModules:
          10: '@vendor/my-extension/backend/form-editor/view-model.js'
      formEditorPartials:
        FormElement-MyCustomElement: 'Stage/MyCustomElement'
      formEditorFluidConfiguration:
        partialRootPaths:
          100: 'EXT:my_extension/Resources/Private/Backend/Partials/FormEditor/'

```

*JavaScript module:*

**EXT:my_extension/Resources/Public/JavaScript/backend/form-editor/view-model.js**

```javascript
export function bootstrap(formEditorApp) {
    formEditorApp.getPublisherSubscriber().subscribe(
        'view/stage/abstract/render/template/perform',
        (topic, args) => {
            const [formElement, template] = args;

            if (formElement.get('type') !== 'MyCustomElement') {
                return;
            }

            const labelEl = template.querySelector('[data-identifier="elementLabel"]');
            if (labelEl) {
                labelEl.textContent =
                    formElement.get('label') || formElement.get('identifier');
            }

            const summaryEl = template.querySelector('[data-identifier="elementSummary"]');
            if (summaryEl) {
                summaryEl.textContent =
                    formElement.get('properties.myCustomProperty') ?? '';
            }
        },
    );
}

```

### view/stage/abstract/render/preProcess {#apireference-formeditor-jsevents-view-stage-abstract-render-preprocess}

Published immediately before the abstract stage area is re-rendered.

-   *Arguments:* none

### view/stage/abstract/render/postProcess {#apireference-formeditor-jsevents-view-stage-abstract-render-postprocess}

Published immediately after the abstract stage area has been rendered.
EXT:form uses this to re-render the undo/redo buttons and apply validation
error highlights.

-   *Arguments:* none

### view/stage/preview/render/postProcess {#apireference-formeditor-jsevents-view-stage-preview-render-postprocess}

Published after the preview stage area has been rendered. EXT:form uses
this to re-render the undo/redo buttons.

-   *Arguments:* none

### view/stage/element/clicked {#apireference-formeditor-jsevents-view-stage-element-clicked}

Published when a form element in the abstract stage is clicked. EXT:form
selects the element, shows its toolbar and re-renders the inspector.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | `__identifierPath` of the clicked element. |

### view/stage/panel/clicked {#apireference-formeditor-jsevents-view-stage-panel-clicked}

Published when the stage panel header or background area is clicked
(not on a specific form element).

-   *Arguments:* none

### view/stage/abstract/button/newElement/clicked {#apireference-formeditor-jsevents-view-stage-abstract-button-newelement}

Published when the "Create new element" button at the bottom of the
stage (in abstract view) is clicked. EXT:form opens the "New element"
dialog configured to insert at the bottom of the current page.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `'view/insertElements/perform/bottom'` | Target publish event for the dialog result. |
| `args[1]` | `object \| undefined` | Optional modal configuration. |

### view/stage/abstract/elementToolbar/button/newElement/clicked {#apireference-formeditor-jsevents-view-stage-abstract-toolbar-newelement}

Published when the "Add element" button or split-button ("Before",
"After", "Inside") in the per-element toolbar is clicked. EXT:form opens
the "New element" dialog with the appropriate target event.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | Target event: `'view/insertElements/perform/before'`, `'…/after'` or `'…/inside'`. |
| `args[1]` | `object` | Modal configuration (`disableElementTypes`, `onlyEnableElementTypes`). |

### view/stage/abstract/dnd/start {#apireference-formeditor-jsevents-view-stage-dnd-start}

Published when a drag operation begins in the abstract stage. EXT:form
adds CSS classes to highlight the dragged element.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `HTMLElement` | The dragged element's DOM node. |
| `args[1]` | `HTMLElement` | The drag placeholder DOM node. |

### view/stage/abstract/dnd/change {#apireference-formeditor-jsevents-view-stage-dnd-change}

Published on each positional change during a drag operation in the stage
(SortableJS `onChange`). EXT:form applies hover CSS classes.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `HTMLElement` | The drag placeholder DOM node. |
| `args[1]` | `string` | `__identifierPath` of the potential parent element. |
| `args[2]` | `FormElement` | Innermost enclosing composite element (if any). |

### view/stage/abstract/dnd/update {#apireference-formeditor-jsevents-view-stage-dnd-update}

Published at the end of a drag operation when the element was dropped in a
new position (SortableJS `onEnd`). EXT:form calls
`moveFormElement()` to persist the new order.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `HTMLElement` | The dropped DOM node. |
| `args[1]` | `string` | `__identifierPath` of the moved element. |
| `args[2]` | `string` | `__identifierPath` of the preceding sibling (empty string if first). |
| `args[3]` | `string` | `__identifierPath` of the following sibling (empty string if last). |

### view/stage/abstract/dnd/stop {#apireference-formeditor-jsevents-view-stage-dnd-stop}

Published after the drag operation completes and all model updates are
done. EXT:form re-renders tree, stage and inspector and selects the moved
element.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | `__identifierPath` of the element that was dragged. |

### view/inspector/editor/insert/perform {#apireference-formeditor-jsevents-view-inspector-editor-insert}

**Extension point for custom inspector editors.**

Published after each inspector editor has been rendered (both for form
elements and for collection elements). Use `args[0].templateName` to
identify which editor is being rendered and apply custom logic.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `EditorConfiguration` | Full YAML configuration of the editor (includes `templateName`). |
| `args[1]` | `HTMLElement` | The rendered DOM node of the inspector editor. |
| `args[2]` | `string` | Identifier of the active collection element (validator/finisher), or empty string when rendering a plain element editor. |
| `args[3]` | `string` | Collection name (`'validators'` or `'finishers'`), or empty. |

**Example — register a custom inspector editor:**

*Prototype YAML:*

```yaml
prototypes:
  standard:
    formEditor:
      dynamicJavaScriptModules:
        additionalViewModelModules:
          10: '@vendor/my-extension/backend/form-editor/view-model.js'
      formEditorPartials:
        Inspector-MyCustomEditor: 'Inspector/MyCustomEditor'
      formEditorFluidConfiguration:
        partialRootPaths:
          100: 'EXT:my_extension/Resources/Private/Backend/Partials/FormEditor/'
    formElementsDefinition:
      Text:
        formEditor:
          editors:
            600:
              templateName: 'Inspector-MyCustomEditor'
              myOption: 'example'

```

*JavaScript module:*

**EXT:my_extension/Resources/Public/JavaScript/backend/form-editor/view-model.js**

```javascript
export function bootstrap(formEditorApp) {
    formEditorApp.getPublisherSubscriber().subscribe(
        'view/inspector/editor/insert/perform',
        (topic, args) => {
            const [editorConfiguration, editorHtml] = args;

            if (editorConfiguration.templateName !== 'Inspector-MyCustomEditor') {
                return;
            }

            // Wire up your custom editor UI inside editorHtml
            const input = editorHtml.querySelector('.my-custom-input');
            if (input) {
                input.addEventListener('change', (e) => {
                    formEditorApp
                        .getCurrentlySelectedFormElement()
                        .set(editorConfiguration.propertyPath, e.target.value);
                });
            }
        },
    );
}

```

### view/inspector/collectionElement/new/selected {#apireference-formeditor-jsevents-view-inspector-collection-new-selected}

Published when the user selects a *new* validator or finisher from the
select box in the inspector. EXT:form adds the collection element to the
form definition and re-renders the inspector.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | Identifier of the selected collection element. |
| `args[1]` | `string` | Collection name (`'validators'` or `'finishers'`). |

### view/inspector/collectionElement/existing/selected {#apireference-formeditor-jsevents-view-inspector-collection-existing-selected}

Published when the user expands an *existing* validator or finisher row
in the inspector. EXT:form renders that element's sub-editors.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | Identifier of the already-selected collection element. |
| `args[1]` | `string` | Collection name. |

### view/inspector/collectionElements/dnd/update {#apireference-formeditor-jsevents-view-inspector-collection-dnd-update}

Published when a validator or finisher is reordered via drag-and-drop
inside the inspector (SortableJS `onEnd`). EXT:form moves the element
in the form definition.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | Identifier of the moved element. |
| `args[1]` | `string` | Identifier of the preceding element after the move. |
| `args[2]` | `string` | Identifier of the following element after the move. |
| `args[3]` | `string` | Collection name. |

### view/inspector/removeCollectionElement/perform {#apireference-formeditor-jsevents-view-inspector-removecollectionelement}

Published by the `RequiredValidatorEditor` when its checkbox is
unchecked. EXT:form removes the `NotEmpty` validator from the form
definition.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | Validator identifier (e.g. `'NotEmpty'`). |
| `args[1]` | `'validators'` | Collection name (always `'validators'` for this event). |
| `args[2]` | `FormElement \| undefined` | The owning form element, or `undefined` for the currently selected one. |

### view/modal/close/perform {#apireference-formeditor-jsevents-view-modal-close}

Published when the user confirms closing the editor in the "unsaved
changes" dialog. EXT:form clears the unsaved-content flag and navigates
back to the form manager.

-   *Arguments:* none

### view/modal/removeFormElement/perform {#apireference-formeditor-jsevents-view-modal-removeformelement}

Published when the user confirms deleting a form element in the
confirmation dialog. EXT:form removes the element from the form
definition.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `FormElement` | The form element to be deleted. |

### view/modal/removeCollectionElement/perform {#apireference-formeditor-jsevents-view-modal-removecollectionelement}

Published when the user confirms removing a validator or finisher via its
delete icon. EXT:form removes the collection element from the form
definition.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | Identifier of the collection element to remove. |
| `args[1]` | `string` | Collection name. |
| `args[2]` | `FormElement` | The owning form element. |

### view/modal/validationErrors/element/clicked {#apireference-formeditor-jsevents-view-modal-validationerrors-clicked}

Published when the user clicks a form element link inside the validation
error dialog. EXT:form selects the element and navigates to it.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | `__identifierPath` of the element with the validation error. |

### view/structure/root/selected {#apireference-formeditor-jsevents-view-structure-root-selected}

Published when the root element in the structure tree is clicked. EXT:form
selects the root form element and re-renders stage, tree and inspector.

-   *Arguments:* none

### view/structure/button/newPage/clicked {#apireference-formeditor-jsevents-view-structure-button-newpage}

Published when the "Create new page" button inside the structure tree panel
is clicked. EXT:form opens the "New page" dialog.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `'view/insertPages/perform'` | Target publish event for the dialog result. |

### view/structure/renew/postProcess {#apireference-formeditor-jsevents-view-structure-renew-postprocess}

Published after the structure tree has been fully re-rendered. EXT:form
uses this to apply validation error markers to tree nodes.

-   *Arguments:* none

### view/tree/node/clicked {#apireference-formeditor-jsevents-view-tree-node-clicked}

Published when a node in the structure tree is clicked. EXT:form selects
the element and re-renders stage and inspector.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | `__identifierPath` of the clicked element. |

### view/tree/node/changed {#apireference-formeditor-jsevents-view-tree-node-changed}

<!-- TODO: no Markdown rendering for "versionadded" -->

This event was previously missing from the documentation. It has been
dispatched since inline label editing in the structure tree was introduced.

Published when a tree node label is edited inline (inline-rename). EXT:form
writes the new label to the FormElement model and updates the inspector if
the element is currently selected.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | `__identifierPath` of the renamed element. |
| `args[1]` | `string` | The new label string. |

### view/tree/render/listItemAdded {#apireference-formeditor-jsevents-view-tree-listitem-added}

> [!NOTE]
> This event is defined in the TypeScript event-map interface but is
> **not yet published** by the core tree component. It is reserved for
> future use. Subscribing to it will currently have no effect.

Published by the tree component for each form element as it is added to
the rendered tree. Use this to augment individual tree nodes.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `HTMLElement \| null` | The list item DOM node that was added. |
| `args[1]` | `FormElement` | The FormElement model for this tree node. |

### view/tree/dnd/change {#apireference-formeditor-jsevents-view-tree-dnd-change}

Published on each positional change during a drag in the structure tree
(SortableJS `onChange`). EXT:form applies hover CSS classes.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `HTMLElement \| null` | The drag placeholder node. |
| `args[1]` | `string` | `__identifierPath` of the potential parent element. |
| `args[2]` | `FormElement` | Innermost enclosing composite element (if any). |

### view/tree/dnd/update {#apireference-formeditor-jsevents-view-tree-dnd-update}

Published when a drag in the structure tree ends and the element was
dropped in a new position. EXT:form calls `moveFormElement()`.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `HTMLElement \| null` | The dropped DOM node. |
| `args[1]` | `string` | `__identifierPath` of the moved element. |
| `args[2]` | `string` | `__identifierPath` of the preceding sibling. |
| `args[3]` | `string` | `__identifierPath` of the following sibling. |

### view/tree/dnd/stop {#apireference-formeditor-jsevents-view-tree-dnd-stop}

Published after the tree drag operation completes. EXT:form re-renders
tree, stage and inspector and selects the moved element.

-   *Arguments:*

| Index | Type | Description |
| --- | --- | --- |
| `args[0]` | `string` | `__identifierPath` of the element that was dragged. |
