Custom actions
Add host actions to the editor selection and right-click menu
Add your own buttons with customActions and observe their lifecycle with onCustomAction.
Usage
import { ImageEditor, type ImageCustomAction } from '@layermetry/media-editor/react';
const inspectPicture: ImageCustomAction = {
id: 'inspect-picture',
label: 'Inspect picture',
placements: ['selection', 'contextMenu'],
when: { itemTypes: ['image'] },
run(ctx) {
const item = ctx.items[0];
console.log('Selected picture', item.id, item.width, item.height);
ctx.notify(`Selected picture: ${item.id}`, 'info');
},
};
export default function App() {
return <div style={{ position: 'fixed', inset: 0 }}>
<ImageEditor licenseKey="YOUR_LICENSE_KEY" onClose={() => undefined}
customActions={[inspectPicture]}
onCustomAction={(event) => console.log(event.actionId, event.status)} />
</div>;
}Add a picture, select it, then press Inspect picture. In 1.0.0, image actions receive ctx.verbs === null. They can call your services, inspect selected item data and show notices; they cannot edit the document through a custom-action handle. The image editor ignores undo.
Action fields
Each action needs a unique id, a label and a run function.
| Field | Type | Default | Description |
|---|---|---|---|
id | string | Required | Names the action in events and as custom:<id> in itemActions. |
label | string | Required | Sets the button label, tooltip and accessible name. |
run | (ctx) => void or Promise<void> | Required | Runs your workflow when the user presses the button. |
icon | CustomActionIcon | None | Uses { svg } or { url } icon data; React components are not accepted here. |
variant | 'button' or 'icon' | 'button' | Chooses words with the icon or an icon-only selection button. |
placements | Array of 'selection' or 'contextMenu' | ['selection'] | Chooses the selection controls, right-click menu, or both. |
layouts | Array of 'regular' or 'compact' | Both | Chooses layouts above 767 px or at most 767 px wide. |
when.itemTypes | Array of item types | Any | Requires every selected item to match a listed type. |
when.selection | 'single', 'multiple' or 'any' | 'single' | Requires one, more than one, or any nonempty selection. |
when.matches | (items) => boolean | None | Adds a synchronous check of the selected items. |
undo | 'one-step' or 'per-verb' | 'one-step' | Groups video edits into one Undo step or keeps one step per editing action. |
priority | number | 0 | Orders custom actions from highest to lowest, preserving list order for ties. |
group | string | None | Names the right-click overflow submenu when its actions all share the same group. |
onError | (error, ctx) => void or 'handled' | None | Handles a thrown or rejected run. |
when.matches runs frequently, including during a drag. Keep it fast and synchronous. It receives shallow frozen item copies. Throwing hides the action and produces one warning. A viewer sees no custom actions. Hiding an action does not grant permission to edit a locked item.
Run context
run receives the selected items as they were when the button was pressed.
| Field | Type | Description |
|---|---|---|
editor | 'video' or 'image' | Identifies the editor. |
actionId | string | Identifies this action. |
items | Read-only item array | Holds deep frozen copies of plain item data; decoded pictures, files and functions are omitted. |
verbs | Editing handle or null | Exposes execute, executeAsync, list and getItems in the video editor; it is null in the image editor. |
signal | AbortSignal | Aborts when the editor closes or the host removes the running action. |
notify | (message, status?) => void | Shows a five-second notice, with status 'info' by default. |
confirm | (message, options?) => Promise<boolean> | Asks a question with Continue and Cancel by default. |
setProgress | (label: string or null) => void | Changes the busy button's text or clears it with null. |
confirm accepts confirmLabel and cancelLabel. A closed editor answers false and shows no further notices. Use signal for host requests. A request that ignores it can continue on your server after the editor closes.
Only one custom action runs at a time in an editor. Other custom buttons are disabled while it runs. There is no automatic run timeout. An action that fails keeps edits it already made; Undo can restore them. The SDK does not roll back your server calls.
Placement and overflow
Custom actions appear after the editor's own controls.
| Surface | ui.customActions.shown field | Default | Maximum |
|---|---|---|---|
| Compact selection bar | compact.selection | 2 | 4 |
| Video desktop floating pill | regular.selection | 2 | 4 |
| Bar above the canvas, desktop | regular.toolbar | 3 | 6 |
| Right-click menu | regular.menu | 5 | 10 |
On a desktop, your actions appear on the bar above the canvas. The bar shows the first 3 of them. Bar actions use icons when supplied, with their labels as tooltips; actions without an icon show their words. The rest are in a More actions menu that holds only custom actions.
Remaining actions go into a More actions menu. The right-click menu uses one submenu, named by a common group or More actions. The list accepts at most 24 actions. Extra actions are omitted with a warning. Built-in controls are never displaced.
const ui = {
customActions: {
shown: {
regular: { selection: 2, toolbar: 3, menu: 5 },
compact: { selection: 2 },
},
},
};Counts must be whole numbers of zero or more. Zero puts all custom actions in overflow. A count above the maximum is lowered with a warning. An invalid count uses the default. No custom actions appear when customActions is omitted.
Events and errors
onCustomAction receives started, then one final status.
| Field | Type | Description |
|---|---|---|
actionId | string | Identifies the action. |
status | 'started', 'done', 'failed' or 'aborted' | Reports the run state. |
error | Optional string | Holds the error message for a failed run. |
durationMs | Optional number | Reports elapsed milliseconds on final events. |
A thrown or rejected run calls onError. Returning 'handled' suppresses the default «label» could not finish. notice; the event still reports failed. Removing an action or closing the editor aborts its signal and sends aborted. Video edits requested after closure return ok: false with reason 'editor-closed'.
There is no declarative step-by-step workflow format in 1.0.0. Your run function is the workflow.
Custom element
Set actions through the element's props property; functions cannot be HTML attributes.
<!doctype html>
<html lang="en">
<head><meta charset="utf-8" /><title>Custom editor actions</title></head>
<body>
<media-editor-image id="editor" style="position: fixed; inset: 0"></media-editor-image>
<script type="module">
import '@layermetry/media-editor';
const el = document.getElementById('editor');
el.props = {
licenseKey: 'YOUR_LICENSE_KEY',
onClose: () => el.remove(),
customActions: [{
id: 'inspect-selection', label: 'Inspect selection',
run: (ctx) => ctx.notify(`Selected: ${ctx.items[0].id}`),
}],
};
el.addEventListener('customaction', (event) => {
console.log(event.detail.actionId, event.detail.status);
});
</script>
</body>
</html>Use a module bundler such as Vite to resolve the package import. The customaction event carries the same payload as onCustomAction. See Events.