---
title: "Extending the frontend UI"
manual: "Frontend Edit"
version: "2.6"
source: "DeveloperCorner/JavaScriptApi.rst"
rendered: "2026-10-01T07:11:45+00:00"
---

# Extending the frontend UI {#javascript-api}

For UI that needs to run in the browser - status badges, comment indicators,
custom toolbar buttons - the extension exposes a small, stable facade on
`window.XimaFrontendEdit` and dispatches DOM `CustomEvent`s at
defined lifecycle points. Internal refactors of the underlying JavaScript do
not change these method signatures or event `detail` shapes - this is
the one part of the frontend JavaScript covered by a semver guarantee.

> [!NOTE]
> Everything described here requires frontend editing to be active for the
> current backend user (see [Introduction](../Introduction/Index.html#introduction)). When
> editing is disabled, `xfe:ready` still fires once with an empty
> element map, but no elements are ever registered.

## Facade methods {#facade-methods}

### `getElementInfo(uid)` {#getelementinfo-uid}

Returns the resolved target element and payload for a content element uid, so
consumers never have to re-do DOM resolution (anchor pattern, translation
mapping). Returns `null` if the uid was not rendered.

```javascript
const info = window.XimaFrontendEdit.getElementInfo(42);
// { uid: 42, element: HTMLElement, payload: { element: {...}, menu: {...} } }
```

### `notify({ title, message, severity })` {#notify-title-message-severity}

Shows a toast notification using the extension's own notification manager.
`severity` is one of `ok`, `info`, `warning`,
`error`.

```javascript
window.XimaFrontendEdit.notify({
    title: 'Comment added',
    message: 'A new comment was posted on this element.',
    severity: 'info',
});
```

### `registerToolbarItem(uid, buttonSpec)` {#registertoolbaritem-uid-buttonspec}

Adds a button to a content element's hover toolbar, next to the built-in
Edit/More actions buttons. `buttonSpec`:

-   `html` \- inner HTML of the button (e.g. an inline SVG icon)
-   `label` \- accessible label, also used as the tooltip
-   `href` \- renders an `<a>` instead of a `<button>`
-   `onClick` \- click handler

```javascript
window.XimaFrontendEdit.registerToolbarItem(42, {
    html: '<svg>...</svg>',
    label: 'Show comments',
    onClick: () => openCommentsPanel(42),
});
```

### `registerBadge(uid, spec)` {#registerbadge-uid-spec}

Renders a persistent, hover-independent indicator on a content element's
overlay - useful for "live annotating" a page, where a marker must be
visible at a glance rather than only on hover. `spec`:

-   `html` or `element` \- the badge content (HTML string or a DOM element)
-   `position` \- one of `top-left`, `top-right` (default), `bottom-left`, `bottom-right`
-   `id` \- when given, a later call with the same uid + id replaces this badge in place instead of adding a duplicate (useful since `xfe:element-rendered` can fire more than once for the same element)
-   `onClick` \- click handler; the badge only receives pointer events when this is set

Multiple badges registered for the same corner lay out in a row, in
registration order, instead of overlapping - each corner is its own slot,
created on first use.

```javascript
window.XimaFrontendEdit.registerBadge(42, {
    html: '<span class="my-badge" title="3 comments">3</span>',
    position: 'top-left',
    id: 'comment-count',
    onClick: () => openCommentsPanel(42),
});
```

### `setBadgeMode(mode)` {#setbadgemode-mode}

Sets a display-mode hook for badges: `'subtle'` (default) or `'prominent'`,
exposed as `document.documentElement`'s `data-xfe-badge-mode`
attribute. The extension ships no badge content itself (see
`registerBadge` above), so it has no opinion on what "subtle" vs
"prominent" should actually look like - write your own CSS against the
attribute:

```javascript
window.XimaFrontendEdit.setBadgeMode('prominent');
```

**A consumer's own stylesheet**

```css
/* Small dot only, by default */
.my-badge-label { display: none; }
/* Full label once prominent mode is active */
[data-xfe-badge-mode="prominent"] .my-badge-label { display: inline; }
```

### `openBackendView(url, options)` {#openbackendview-url-options}

Opens a backend URL in the version-appropriate container: the contextual
sidebar (v14.2+, when enabled and available), the v13 iframe modal, or a new
tab as a fallback - no version checks needed in consumer code. The returnUrl
flash-message deferral mechanism used by edit forms is applied automatically.
`options`:

-   `target` \- `'tab'` forces a new tab regardless of version
-   `title` \- shown in the container's header
-   `width` \- a CSS length (e.g. `'600px'`, `'50%'`); invalid values are ignored
-   `onClose` \- called with `{ reason }` when the container closes (`reason` is `'close'`, or `'saved'` for the sidebar's own save flow)
-   `reloadOnClose` \- reload the parent page on close (default `true`)
-   `linkPolicy` \- a rule or array of rules `{ match: string | RegExp, action }`, evaluated against link clicks inside the embedded document. `action` is one of:

    -   `stay` (default when nothing matches) - keep navigation inside the container, same as today's built-in behavior
    -   `close` \- close the container
    -   `ignore` \- swallow the click, do nothing
    -   `external` \- open the link in a new tab

    The policy only ever governs plain link clicks - the built-in save/close buttons of an actual edit form keep working exactly as before.

```javascript
window.XimaFrontendEdit.openBackendView(commentsBackendUrl, {
    title: 'Comments',
    width: '480px',
    linkPolicy: { match: '/typo3/module/web/layout', action: 'close' },
    onClose: ({ reason }) => console.log('comments panel closed:', reason),
});
```

## Lifecycle events {#lifecycle-events}

All events are dispatched on `document`.

### `xfe:ready` {#xfe-ready}

Fired once, after the initial AJAX render (or immediately, with an empty map,
when frontend editing is disabled).

-   `detail.elements` \- plain object keyed by content element uid, each value shaped like the `getElementInfo()` return value

### `xfe:element-rendered` {#xfe-element-rendered}

Fired once per content element, right after its overlay/toolbar is built.

-   `detail.uid`, `detail.element`, `detail.payload` \- same as `getElementInfo()`
-   `detail.overlay`, `detail.toolbar`, `detail.dropdown` \- the underlying DOM nodes (`dropdown` is `null` when the element has no context menu)

### `xfe:dropdown-open` / `xfe:dropdown-close` {#xfe-dropdown-open-xfe-dropdown-close}

Fired when a content element's "More actions" dropdown opens or closes.
`detail.uid` identifies the element. Only fires for an actual
open/close interaction - hovering between elements does not trigger a
spurious `xfe:dropdown-close` for a dropdown that was never open.

```javascript
document.addEventListener('xfe:element-rendered', (event) => {
    const { uid, payload } = event.detail;
    if (payload.element.CType === 'list' && payload.element.list_type === 'news_pi1') {
        window.XimaFrontendEdit.registerBadge(uid, { html: '<span>News</span>' });
    }
});
```

## Minimal integration example {#minimal-integration-example}

**A third-party extension listening for rendered elements**

```javascript
document.addEventListener('xfe:ready', (event) => {
    console.log('Frontend Edit ready with', Object.keys(event.detail.elements).length, 'element(s)');
});

document.addEventListener('xfe:element-rendered', (event) => {
    const { uid } = event.detail;
    window.XimaFrontendEdit.registerToolbarItem(uid, {
        label: 'Open comments',
        onClick: () => window.XimaFrontendEdit.openBackendView('/comments/' + uid),
    });
});
```
