Documentation Index
Fetch the complete documentation index at: https://superdoc-caio-pizzol-sd-3105-custom-xml-parts-api.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
ui.commands.register({ id, execute, getState }) puts your command on the same surface as built-ins. Bind to it with useSuperDocCommand(id) exactly like a bold button. Override built-ins when you need to. Recompute state when something outside the editor changes.
Register a command
unregister(). Always tear down in the effect’s cleanup so unmounting the component removes the command.
Bind a button to it
Same hook as built-ins.example.insertClause command alongside the built-in bold / italic / underline buttons.
Naming
Use a namespace to avoid colliding with future built-ins.company.insertClause, acme.aiRewrite, support.translate. Bare names like 'rewrite' will warn if SuperDoc later adds a built-in with the same id.
getState
getState runs on every snapshot rebuild. Keep it cheap. The argument is the full controller state, so you can read state.selection, state.documentMode, state.comments without subscribing yourself.
getState is sync. Async work belongs in execute. If app state outside the editor changes (auth flip, quota tick), call reg.invalidate() to re-run getState.
execute
execute receives { payload, superdoc, editor, context }. The cleanest custom commands are additive: they call your services, open your modals, fire telemetry, and don’t mutate the document. The runtime cares about the return value (boolean or Promise<boolean>); the engine doesn’t see the work.
| Field | Meaning |
|---|---|
payload | Whatever the caller passed to commands.get(id).execute(payload). Typed via register<TPayload>(...). |
superdoc | The host SuperDoc instance. Useful when you need superdoc.config or other host-only fields. |
editor | The currently routed editor. Tracks header / footer / footnote focus, so editor.doc.* calls land on the right surface. null until ready. |
context | The ViewportContext bundle when the command was invoked from a right-click menu via ContextMenuItem.invoke(). undefined for direct dispatch. See Custom right-click menu. |
Mutating the document from a custom command
Custom commands that need to insert, replace, or format content reach the Document API through the host instance today. Use the publicui.selection slice for positional shapes; pull superdoc.activeEditor for the doc-API call. The reference demo’s InsertClauseButton shows the full pattern.
- The positional target comes from
ui.selection.getSnapshot().selectionTarget(a publicSelectionTargetshape thateditor.doc.insertaccepts). Don’t passselection.current().target. That’s aTextTarget, the wrong shape forinsert. editoris typed and resolved late-bound by the controller. It tracks header / footer / footnote focus the same way every otherui.*mutation does, so a custom command run from a header-focused composer hits the right story.
InsertClauseButton ships this exact pattern.
Keyboard shortcut
Addshortcut: 'Mod-Shift-K' to bind a key combo. The controller installs one bubble-phase listener scoped to the painted host, matches the combo, and dispatches execute through the same path the toolbar button uses. No per-command keymap wiring.
Mod (Ctrl on Windows / Linux, Cmd on macOS), Shift, Alt, Ctrl, then a key (A through Z, 0 through 9, Enter, Space, etc.). Examples: 'Mod-K', 'Mod-Shift-C', 'Alt-Enter'.
The keyboard path doesn’t consult getState. If your toolbar button is disabled, the shortcut still fires unless you mirror the gate inside execute:
Contribute to the right-click menu
Add acontextMenu field to surface the command in your custom right-click menu. The when predicate filters on the bundle from ui.viewport.contextAt({ x, y }).
| Field | Type | Meaning |
|---|---|---|
label | string | Item text. Required. |
group | string | Sort key. Built-in groups: format, clipboard, review, comment, link. Customs land after, in registration order. Default 'custom'. |
order | number | Sort order within a group. Default 0. |
when | (input) => boolean | Filter on { entities, selection, point?, position?, insideSelection? }. Returns true to show the item. |
ui.commands.getContextMenuItems(context) carry an invoke() closure that fires execute({ context }) with the bundle bound. Your menu component dispatches with one call, no payload threading. See Custom right-click menu for the full pattern.
Look up commands by id
ui.commands.get(id) returns a typed handle. ui.commands.has(id) reports whether the id is registered. ui.commands.require(id) returns the handle or throws when missing, useful when the registration is set up earlier in your component tree and the lookup site can rely on it.
Override a built-in
Passoverride: true to deliberately replace a built-in. Without it, registrations colliding with a built-in id are refused with a console warning.
Two ways to reach the same toolbar effect:
useSuperDocCommand('bold'), ui.commands.get('bold')?.execute(), ui.toolbar.execute('bold').
Trade-offs
getStateruns on every snapshot rebuild. Keep it pure and fast.executeerrors are caught and logged; they don’t crash the toolbar.unregister()is idempotent. Calling twice is safe.- The lifecycle is component-scoped. Hold the registration for as long as the command should be available.

