Extension framework changes for Editor 2.0 (New Editor)

This document covers all APIs added to guides.editor (and guides) as part of the extension framework for the New Editor (ProseMirror-based editor). These APIs allow external extensions to interact with the editor without direct DOM manipulation or internal implementation knowledge.

Overview

The global guides object is the entry point for all extension integrations:

guides.editor    // Editor interaction APIs
guides.util      // Utility libraries (lodash, async)
guides.ready(cb) // Lifecycle hook fires when the editor is fully loaded

All guides.editor APIs are safe to call from extension controllers, toolbar handlers, and dialog logic.

Lifecycle

guides.ready(callback): Registers a callback to execute once the page view manager has fully loaded. Use this before registering plugins or performing any editor setup.

Signature:

guides.ready(callback: () => void): void

Example:

guides.ready(() => {
  // Safe to access guides.editor APIs here
  guides.editor.registerPlugin(createMyPlugin);
});

Editor state properties

  • guides.editor.filePath: Returns the file path of the currently active document.

    Type: string | undefined

    Example: reading file path for a metadata API call:

    code language-js
    const filePath = guides.editor.filePath;
    if (filePath) {
    tcx.api.getMetadata(filePath).subscribe((metadata) => {
      // use metadata...
      });
    }
    

    Example: conditional logic based on file location

    code language-js
    const filePath = guides.editor.filePath;
    if (filePath?.endsWith(".ditamap")) {
      // ditamap-specific handling
    }
    
  • guides.editor.version: Returns the version string of the currently loaded editor.

    table 0-row-2 1-row-2 2-row-2
    Value Editor
    1.0.0 Legacy CKEditor (xml_author_view)
    2.0.0 New Editor (ProseMirror-based)

    Type: string

    Example:

    code language-js
    if (guides.editor.version  === '1.0.0') {
      // CKEditor specific logic
    }
    
  • guides.editor.appState(keyName): Reads a value from the application model by key name.

    Signature:

    code language-ts
    guides.editor.appState(keyName: string): unknown
    

    Example:

    code language-js
    const editorMode = guides.editor.appState('editorMode');
    

Editor interaction

  • guides.editor.focus(): Sets focus to the active editor. Call this before executing commands that require the editor to have focus.

    Signature:

    code language-ts
    guides.editor.focus(): boolean
    

    Example: focus before inserting content

    code language-js
    guides.editor.focus();
    const hasSelection = !!guides.editor.runUtil('hasSelection');
    // ...proceed with wrap or insert
    
  • guides.editor.canInsertXmlElement(tagName, insertAfter?): Checks whether a given XML element can be inserted at the current cursor position.

    Signature:

    code language-ts
    guides.editor.canInsertXmlElement(tagName: string, insertAfter?: boolean): boolean
    

    Example: guard before inserting <xref>

    code language-js
    const canInsert = guides.editor.canInsertXmlElement("xref");
    if (!canInsert) {
      return tcx.util.showAlert("warning", "xref is not allowed here");
    }
    

    Example: guard before inserting <sup> / <sub>

    code language-js
    const canInsert = guides.editor.canInsertXmlElement("sup");
    if (!canInsert) {
      return tcx.util.showAlert("warning", "superscript is not allowed here");
    }
    
  • guides.editor.selectCurrentBlockElement(): Selects the current block-level element in the editor.

    Signature:

    code language-ts
    guides.editor.selectCurrentBlockElement(): boolean
    

    Example:

    code language-js
    guides.editor.selectCurrentBlockElement();
    
  • guides.editor.getValidElementNamesForInsertion(args): Returns a list of valid XML element names that can be inserted at the current position.

    Signature:

    code language-ts
    guides.editor.getValidElementNamesForInsertion(args: {
      insertMode?: 'after' | 'before' | 'rename',
      strict: boolean
    }): string[] | false
    

    Example:

    code language-js
    const validElements = guides.editor.getValidElementNamesForInsertion({
      insertMode: 'after',
      strict: true
    });
    
  • guides.editor.updateAttributeByXpath(args): Updates a specific XML attribute on a node identified by its XPath. Delegates to the active editor’s updateAttributeByXpath method.

    Signature:

    code language-ts
    guides.editor.updateAttributeByXpath(args: UpdateXpathArgs): any
    

Command execution

  • guides.editor.runCommand(commandName, ...args): Executes a named command on the New Editor. Returns true if the command succeeded, false otherwise.

    Signature:

    code language-ts
    guides.editor.runCommand(commandName: string, ...args: any[]): boolean
    

    Available commands

    note
    NOTE
    Stability: The commands listed below are part of the public interface. Their names, signatures, and observable behavior are considered stable and are maintained for compatibility. Additional commands may be present within the editor; however, these are internal, are not intended for external use, and may change or be removed without prior notice.
    table 0-row-4 1-row-4 2-row-4 3-row-4 4-row-4 5-row-4 6-row-4 7-row-4 8-row-4 9-row-4 10-row-4 11-row-4 12-row-4 13-row-4 14-row-4 15-row-4 16-row-4 17-row-4 18-row-4 19-row-4 20-row-4 21-row-4 22-row-4 23-row-4 24-row-4 25-row-4 26-row-4 27-row-4 28-row-4 29-row-4 30-row-4 31-row-4 32-row-4
    Category Command Arguments Description
    General undo (none) Undoes the last document change
    General redo (none) Redoes the last undone change
    Selection select from: number, to?: number Selects the range from from to to (or just from if to is omitted)
    Selection cursor pos: number Moves the cursor to pos
    Selection selectNodesFromXpaths xpaths: Array<{path: Array<{name: string, count: number}>}> Selects nodes identified by XPath positions
    Formatting toggleBold (none) Toggles bold formatting on the current selection
    Formatting toggleItalic (none) Toggles italic formatting on the current selection
    Formatting toggleUnderline (none) Toggles underline formatting on the current selection
    Formatting toggleFormatting markType: string, allowEmptySelection?: boolean Toggles a formatting element (e.g., 'b', 'i', 'sup', 'sub') on the selection
    Insertion insertText text: string Inserts plain text at the cursor
    Insertion insertXml xml: string, position?: number, options?: InsertXmlOptions Inserts raw XML at the cursor or given position
    Insertion insertXmlAfterElement elementName: string, xmlString: string Inserts XML immediately after the nearest ancestor matching elementName
    Insertion replaceSelectionWithXml xmlString: string Replaces the current selection with the given XML
    Node Operations surroundWithElement tagName: string, attrs?: Record<string,unknown>, replaceTextWithEmptyNode?: boolean Wraps the current selection with the given element
    Node Operations wrapNode type: string, target?: number, attrs?: Record<string, unknown> Wraps the node at target (or the current node) in an element of type
    Node Operations renameNode newNodeName: string Renames the current node’s element
    Node Operations unwrapNode (none) Removes the current node’s wrapper element, keeping its content
    Delete deleteSelection (none) Deletes the currently selected content
    Delete deleteNode (none) Deletes the entire current node
    Attributes setNodeXmlAttributes position: number, attrs: Record<string, unknown> Sets multiple XML attributes on the node at position
    Attributes setNodeXmlAttribute position: number, attrName: string, value: string Sets a single XML attribute on the node at position
    Lists toggleList listName: 'ol' | 'ul' Toggles the current block between a list of the given type and a paragraph
    Tables addRowAfter count?: number Adds count rows below the current row (default 1)
    Tables addColumnAfter count?: number Adds count columns to the right of the current column (default 1)
    Tables deleteRow (none) Deletes the current row
    Tables deleteColumn (none) Deletes the current column
    Tables mergeCells (none) Merges the currently selected cells
    Clipboard copy (none) Copies the current selection to the clipboard
    Clipboard cut (none) Cuts the current selection to the clipboard
    Find & Replace setSearchQuery query: string, options?: { caseSensitive?: boolean, regex?: boolean } Sets the active search query
    Find & Replace findNext (none) Moves to the next search match
    Find & Replace replaceAll replacement?: string Replaces every match of the current search query with replacement
    • Example: Set multiple attributes on a node

      code language-js
      guides.editor.runCommand(
        "setNodeXmlAttributes",
        rootRange.from,
        { createdDate: "2024-01-01", author: "Jane Doe" }
      );
      
    • Example: Set a single attribute on a node

      code language-js
      guides.editor.runCommand(
        "setNodeXmlAttribute",
        range.from,
        "placeholdertext",
        "Chapter 3 — Safety Requirements"
      );
      
    • Example: Wrap selection with an element and set attributes

      code language-js
      const didWrap = guides.editor.runCommand(
        "surroundWithElement",
        "ph",
        { outputclass: "highlight" },
        true   // replace text content with empty node
      );
      
    • Example: Wrap selection in <sup> (toggle superscript on)

      code language-js
      const didWrap = guides.editor.runCommand('surroundWithElement', 'sup');
      if (!didWrap) {
        tcx.util.showAlert("warning", "superscript is not allowed here");
      }
      
    • Example: Unwrap current node (toggle superscript off)

      code language-js
      const didUnwrap = guides.editor.runCommand('unwrapNode');
      
    • Example: Insert XML at cursor with caret placed inside

      code language-js
      guides.editor.runCommand(
        'insertXml',
        '<sup></sup>',
        undefined,
        { setCursorInContent: true, focusEditor: true, selectInsertedXml: false }
      );
      
  • guides.editor.canRunCommand(commandName, ...args): Checks whether a named command can currently be executed, without actually running it.

    Signature:

    code language-ts
    guides.editor.canRunCommand(commandName: string, ...args: any[]): boolean
    

    Example:

    code language-js
    if (guides.editor.canRunCommand('surroundWithElement', 'sup')) {
      guides.editor.runCommand('surroundWithElement', 'sup');
    }
    

Utility functions

  • guides.editor.runUtil(utilName, ...args): Invokes a named utility from the New Editor’s utility registry. Returns the utility’s result, or undefined if the utility is not found.

    Signature:

    code language-ts
    guides.editor.runUtil(utilName: string, ...args: any[]): any
    

    Available utilities

    note
    NOTE
    Stability: The utilities listed below are part of the public interface. Their names, signatures, and observable behavior are considered stable and are maintained for compatibility. Additional commands may be present within the editor; however, these are internal, are not intended for external use, and may change or be removed without prior notice.
    table 0-row-5 1-row-5 2-row-5 3-row-5 4-row-5 5-row-5 6-row-5 7-row-5 8-row-5 9-row-5 10-row-5 11-row-5 12-row-5 13-row-5 14-row-5 15-row-5 16-row-5 17-row-5 18-row-5 19-row-5 20-row-5 21-row-5 22-row-5 23-row-5 24-row-5 25-row-5 26-row-5 27-row-5 28-row-5 29-row-5 30-row-5
    Category Utility Arguments Returns Description
    Position getTextPos (none) { start: number, end: number } Start and end positions of the current selection in the ProseMirror document
    Position getNodePosition position?: number number Document position of the currently selected/focused node
    Position mapToXpath position: number XPathPosition Maps a ProseMirror position to an XPath descriptor
    Position inverseMap xpath: XPathPosition | number number Maps an XPath descriptor (or numeric position) back to a ProseMirror position
    Document getNodeTree (none) NodeTree | null Tree representation of the document, suitable for outline rendering
    Document getAncestorsNames position?: number string[] XML tag names of ancestor nodes at position
    Document getAncestorsDetails position?: number { currNode: string, ancestors: Array<{tagName: string}>, previousSibling?: string, nextSibling?: string } | undefined Current node, ancestors, and immediate siblings at position
    Document getAncestorXpaths includeId?: boolean AncestorXpathItem[] XPath strings for all ancestor nodes
    Document getPreviousSibling position?: number string | undefined Tag name of the previous sibling, if any
    Document getNextSibling position?: number string | undefined Tag name of the next sibling, if any
    Document getTagName position?: number string | null Tag name of the element at position (or cursor)
    Element Search findPositionRange tagName: string { from: number, to: number } | undefined Range of the first element with the given tag
    Element Search findPositionRanges tagName: string Array<{ from: number, to: number }> All ranges for elements matching tagName
    Attributes getAttributeAtPosition position: number, attrName: string unknown Reads an XML attribute value from the node at position
    Attributes getSerializableAttributes xpath: string Record<string, unknown> All serializable XML attributes for the node at xpath
    Serialization serialize (none) string Serializes the entire document to XML
    Serialization serializeToText (none) string Serializes the entire document to plain text
    Serialization getRangeXml xpaths: AncestorXpathItem[] string Returns the XML for an XPath-defined range
    Selection getSelectedXml (none) string Currently selected content as an XML string
    Selection getSelectedText (none) string Selected text without any markup
    Selection hasSelection (none) boolean true if there is an active text/node selection
    Selection isSelectionEditable (none) boolean Whether the current selection sits inside editable content
    Selection isPositionEditable position: number boolean Whether position is inside editable content
    Insertion canInsert name: string, position?: number, insertMode?: 'after' | 'before' | 'rename' boolean Whether name can be inserted at position (or cursor)
    Insertion getInsertPosition name: string, position?: number, insertMode?: 'after' | 'before' | 'rename' number | null Valid insertion position for name, or null if not insertable
    Insertion getNodeXml nodeName: string, nodeContent?: string string | null XML template for a node type
    Wrap/Rename getValidWrapNodeElementNames (none) string[] Element names that may wrap the current selection
    Wrap/Rename getValidRenameNodeElementNames (none) string[] Element names the current node may be renamed to
    Tables getTableInfo position?: number { rows: number, cols: number, header: number } Dimensions of the current table
    Tag View isTagViewActive (none) boolean Whether tag-view rendering is active

    Example: get cursor position and ancestor names

    code language-js
    const textPos = guides.editor.runUtil("getTextPos");
    const ancestorNames = guides.editor.runUtil(
      "getAncestorsNames",
      typeof textPos === "number" ? textPos : undefined
    );
    

    Example: find all <xref> elements and read their attributes

    code language-js
    const xrefRanges = guides.editor.runUtil("findPositionRanges", "xref") || [];
    xrefRanges.forEach((range) => {
      const id = guides.editor.runUtil("getAttributeAtPosition", range.from, "id");
      const placeholderText = guides.editor.runUtil(
        "getAttributeAtPosition",
        range.from,
        "placeholdertext"
      );
    });
    

    Example: find the root element range and read its attributes

    code language-js
    const rootRange = guides.editor.runUtil("findPositionRange", "concept"); // or "topic"
    if (rootRange) {
      const createdDate = guides.editor.runUtil(
        "getAttributeAtPosition",
        rootRange.from,
        "createdDate"
      );
      const author = guides.editor.runUtil(
        "getAttributeAtPosition",
        rootRange.from,
        "author"
      );
    }
    

    Example: check selection and validate ancestor context before an operation

    code language-js
    const ancestorsDetails = guides.editor.runUtil('getAncestorsDetails');
    const selectedText = guides.editor.runUtil('getSelectedPlainText');
    const hasSelection = !!guides.editor.runUtil('hasSelection');
    
    const firstAncestor = ancestorsDetails?.currNode || ancestorsDetails?.ancestors?.[0]?.tagName;
    if (firstAncestor === 'ph') {
      tcx.util.showAlert("warning", "Operation is not allowed inside this element type.");
      return;
    }
    

    Example: get element IDs from ancestor XPaths

    code language-js
    const ancestorItems = guides.editor.runUtil('getAncestorXpaths', true);
    for (const item of ancestorItems) {
      const attrs = guides.editor.runUtil('getSerializableAttributes', item.xpath);
      if (attrs?.id) { /* use element ID */ }
    }
    

Decoration API

The Decoration API provides a higher-level alternative to writing full ProseMirror plugins for common visual customizations. Instead of managing Plugin, PluginKey, and DecorationSet manually, you describe what to decorate and how, and the editor’s central decoration manager handles the ProseMirror state internally.

Decorations are identified by a string id so they can be updated or removed independently at any time.

NOTE
New Editor only These APIs are no-ops on the legacy editor (version === '1.0.0').
  • guides.editor.addDecoration(id, selector, options): Adds or replaces a decoration rule. All nodes matching selector in the current document will be decorated according to options. The decoration is re-applied automatically on every document change.

    Signature:

    code language-ts
    guides.editor.addDecoration(
      id: string,
      selector: string,
      options: DecorationOptions
    ): boolean
    

    DecorationOptions fields:

    table 0-row-3 1-row-3 2-row-3 3-row-3
    Field Type Description
    class string CSS class name(s) added to matching nodes
    computeAttributes (node, context) => Record<string, string> Function returning dynamic data-* or other HTML attributes per node
    filter (node) => boolean Optional predicate — only nodes where this returns true are decorated

    The context object passed to computeAttributes includes:

    • index — 0-based position of the node among siblings matching the selector

    Example: add a CSS class to all <section> elements

    code language-js
    guides.editor.addDecoration('highlight-sections', 'section', {
      class: 'my-section-highlight'
    });
    

    Example: add a computed data-number-label attribute to each section

    code language-js
    guides.editor.addDecoration('section-numbers', 'section', {
      computeAttributes: (node, context) => ({
        'data-number-label': String(context.index + 1)
      })
    });
    

    Example: decorate only sections that have importance="high" attribute

    code language-js
    guides.editor.addDecoration('important-sections', 'section', {
      class: 'section-important',
      filter: (node) => node.attrs?.xmlAttrs?.importance === 'high'
    });
    

    Example: combine class and computed attributes

    code language-js
    guides.editor.addDecoration('numbering-mode', 'conbody', {
      class: 'legacy-numbering'
    });
    
    guides.editor.addDecoration('section-numbers', 'section', {
      computeAttributes: (node, context) => ({
        'data-number-label': String(context.index + 1)
      })
    });
    
  • guides.editor.removeDecoration(id): Removes a previously added decoration by its ID.

    Signature:

    code language-ts
    guides.editor.removeDecoration(id: string): boolean
    

    Example:

    code language-js
    guides.editor.removeDecoration('section-numbers');
    
  • guides.editor.batchDecorations(changes): Applies multiple add/remove decoration changes in a single ProseMirror dispatch. Use this when toggling several decorations at once to avoid multiple re-renders.

    Signature:

    code language-ts
    guides.editor.batchDecorations(changes: DecorationBatchChange[]): boolean
    
    type DecorationBatchChange =
      | { action: 'add', id: string, selector: string, options: DecorationOptions }
      | { action: 'remove', id: string }
    

    Example: switch between two numbering modes atomically

    code language-js
    guides.editor.batchDecorations([
      { action: 'remove', id: 'legacy-numbering' },
      {
        action: 'add',
        id: 'division-numbering',
        selector: 'conbody',
        options: { class: 'division-numbering' }
      }
    ]);
    

    Example: clear one decoration and add two new ones

    code language-js
    guides.editor.batchDecorations([
      { action: 'remove', id: 'old-highlights' },
      {
        action: 'add',
        id: 'section-labels',
        selector: 'section',
        options: {
          computeAttributes: (node, ctx) => ({ 'data-section-index': String(ctx.index) })
        }
      },
      {
        action: 'add',
        id: 'note-style',
        selector: 'note',
        options: { class: 'styled-note' }
      }
    ]);
    
  • guides.editor.clearDecorations(): Removes all active decorations managed by the decoration manager.

    Signature:

    code language-ts
    guides.editor.clearDecorations(): boolean
    

    Example:

    code language-js
    guides.editor.clearDecorations();
    
  • guides.editor.getDecorations(): Returns the IDs of all currently active decorations.

    Signature:

    code language-ts
    guides.editor.getDecorations(): string[]
    

    Example:

    code language-js
    const active = guides.editor.getDecorations();
    console.log('Active decorations:', active);
    // e.g. ['section-numbers', 'numbering-mode', 'note-style']
    

Decoration API vs registerPlugin

Scenario
Use
Apply a CSS class or data-* attributes to matching elements
addDecoration
Toggle or update styling based on runtime state (metadata, user action)
addDecoration / removeDecoration / batchDecorations
Complex plugin logic: custom state, key bindings, widget decorations, event handlers
registerPlugin
Injecting CSS into the shadow DOM
registerPlugin with css field

ProseMirror plugin registration

  • guides.editor.registerPlugin(factory): Registers a ProseMirror plugin factory to be included in every New Editor instance. Only factory functions are accepted, direct plugin instances are rejected. The factory is called once per editor instance, ensuring isolated plugin state.

    Signature:

    code language-ts
    guides.editor.registerPlugin(factory: () => PluginConfig): void
    
    interface PluginConfig {
      plugin: ProseMirrorPlugin | ProseMirrorPlugin[]
      css?: string  // Injected into editor's shadow DOM
    }
    

    Example: register plugins after the editor is ready

    code language-js
    guides.ready(() => {
      guides.editor.registerPlugin(createNumberingPlugin);
      guides.editor.registerPlugin(createXrefPlugin);
    });
    

    Example: inline factory with CSS

    code language-js
    guides.editor.registerPlugin(() => ({
      plugin: new guides.editor.prosemirror.state.Plugin({
        key: new guides.editor.prosemirror.state.PluginKey("myPlugin"),
        props: {
          decorations(state) {
            // return DecorationSet...
          }
        }
      }),
      css: `.my-decoration { background: yellow; }`
    }));
    
    note
    NOTE
    CSS passed via css is injected into the editor’s shadow DOM. Regular page-level stylesheets do not apply inside the editor.

ProseMirror libraries

  • guides.editor.prosemirror: Exposes ProseMirror packages directly for use in plugin development. This avoids the need to bundle ProseMirror separately in extension code.

    table 0-row-2 1-row-2 2-row-2 3-row-2 4-row-2 5-row-2 6-row-2 7-row-2 8-row-2 9-row-2 10-row-2
    Property Package
    state prosemirror-state
    model prosemirror-model
    view prosemirror-view
    transform prosemirror-transform
    commands prosemirror-commands
    keymap prosemirror-keymap
    history prosemirror-history
    tables prosemirror-tables
    dropcursor prosemirror-dropcursor
    markdown prosemirror-markdown

    Example: Create a node decoration plugin

    code language-js
    const myPluginKey = new guides.editor.prosemirror.state.PluginKey("myPlugin");
    
    const createMyPlugin = () => {
      const { Plugin } = guides.editor.prosemirror.state;
      const { Decoration, DecorationSet } = guides.editor.prosemirror.view;
    
      return {
        plugin: new Plugin({
          key: myPluginKey,
          state: {
            init() { return { enabled: true }; },
            apply(tr, value) {
              const meta = tr.getMeta(myPluginKey);
              return meta ? { ...value, ...meta } : value;
            }
          },
          props: {
            decorations(state) {
              const decorations = [];
              state.doc.descendants((node, pos) => {
                if (node.type.name === "section") {
                  decorations.push(
                    Decoration.node(pos, pos + node.nodeSize, { class: "my-section" })
                  );
                }
              });
              return DecorationSet.create(state.doc, decorations);
            }
          }
        }),
        css: `.my-section { border-left: 3px solid #ccc; padding-left: 8px; }`
      };
    };
    

    Example: create a widget decoration plugin (inline display text)

    code language-js
    const xrefPluginKey = new guides.editor.prosemirror.state.PluginKey("xrefDisplay");
    
    const createXrefPlugin = () => {
      const { Plugin } = guides.editor.prosemirror.state;
      const { Decoration, DecorationSet } = guides.editor.prosemirror.view;
    
      return {
        plugin: new Plugin({
          key: xrefPluginKey,
          props: {
            decorations(state) {
              const decorations = [];
              state.doc.descendants((node, pos) => {
                if (node.type.name.includes("xref")) {
                  const display = node.attrs?.xmlAttrs?.placeholdertext;
                  if (display) {
                    decorations.push(
                      Decoration.widget(pos + 1, () => {
                        const el = document.createElement("span");
                        el.textContent = `[${display}]`;
                        el.setAttribute("contenteditable", "false");
                        return el;
                      }, { key: `xref-${pos}` })
                    );
                  }
                }
              });
              return DecorationSet.create(state.doc, decorations);
            }
          }
        }),
        css: `.xref-display-text { color: #0074d9; pointer-events: none; }`
      };
    };
    

Injecting CSS into the editor

The Guides DITA editor loads its author-mode content styles from a clientlib with category apps.guides.dita_editor.content. That clientlib has an embed declaration that automatically pulls in any clientlib registered under the category:

apps.guides.xml_editor.dita_content_overrides

To inject custom CSS into the editor’s content area, create an AEM clientlib node with this category. No additional wiring is needed, Guides picks it up automatically when the editor page loads.

AEM clientlib node structure (/apps/my-extension/clientlibs/editor-content-overrides/.content.xml)

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0"
          xmlns:jcr="http://www.jcp.org/jcr/1.0"
    jcr:primaryType="cq:ClientLibraryFolder"
    categories="[apps.guides.xml_editor.dita_content_overrides]"/>

Place your CSS file (css.txt + your .css file) inside this folder. It will be embedded into the editor’s content stylesheet automatically.

Example: override section heading styles in author mode

/* /apps/my-extension/clientlibs/editor-content-overrides/css/content.css */

/* Increase section title font size in author mode */
.section > title {
  font-size: 1.4em;
  font-weight: bold;
  color: #333;
}

/* Highlight note blocks */
.note {
  border-left: 4px solid #0074d9;
  padding-left: 12px;
  background: #f0f7ff;
}
NOTE
This CSS targets the legacy CKEditor-based author surface only. It has no effect inside the New Editor (ProseMirror), which uses a shadow DOM.

New Editor: css in registerPlugin

The New Editor renders inside a shadow DOM, which isolates it from all page-level styles, including AEM clientlibs. To inject CSS into the New Editor’s author surface, pass a css string as part of the PluginConfig returned by your plugin factory.

The CSS is injected as a <style> tag directly inside the editor’s shadow root each time an editor instance is created.

Example: inject styles alongside a decoration plugin

guides.ready(() => {
  guides.editor.registerPlugin(() => ({
    plugin: createMyPlugin(), // your ProseMirror plugin
    css: `
      /* Styles scoped to the New Editor shadow DOM */
      .section > .title-node {
        font-size: 1.4em;
        font-weight: bold;
        color: #333;
      }

      .note-node {
        border-left: 4px solid #0074d9;
        padding-left: 12px;
        background: #f0f7ff;
      }
    `
  }));
});

Example: CSS-only plugin (no decoration logic)

guides.ready(() => {
  guides.editor.registerPlugin(() => ({
    plugin: new guides.editor.prosemirror.state.Plugin({}), // no-op plugin
    css: `
      /* Custom author-mode typography */
      [data-xml-element="codeblock"] {
        font-family: monospace;
        background: #f5f5f5;
        padding: 8px;
        border-radius: 4px;
      }
    `
  }));
});
NOTE
This CSS only applies inside the New Editor shadow DOM. It has no effect on the rest of the page or on the legacy editor.

Context Menu extensions (contextMenuWidget)

Extensions can add items to the editor’s right-click / breadcrumb context menu by declaring a contextMenuWidget field in their extension config. This tells the framework which editor’s menu to target.

Widget IDs

Widget ID
Editor
dita_editor_menu
Legacy CKEditor author surface (breadcrumb + element context menu)
markup_editor_menu
New Editor (ProseMirror) breadcrumb & element context menu

Both widgets are mounted on the page simultaneously. The framework matches each extension to the correct menu based on this field.

Extensions can also target both editors by passing an array: contextMenuWidget: ["dita_editor_menu", "markup_editor_menu"]

Legacy editor: dita_editor_menu

Use this for extensions that should appear in the legacy CKEditor context menu.

const myContextMenuExtension = {
  id: "dita_author_view_menu",
  contextMenuWidget: "dita_editor_menu",
  view: {
    items: [
      {
        displayName: "My Custom Action",
        data: { eventid: "myCustomAction" },
        icon: "textSpaceAfter",
        target: {
          key: "displayName",
          value: "Wrap Element",   // insert after this existing menu item
          viewState: "append",
        },
      },
    ],
  },
  controller: {
    myCustomAction() {
      console.log("Custom action triggered");
    },
  },
};

New Editor: markup_editor_menu

Use this for extensions that should appear in the New Editor context menu (breadcrumbs and right-click on elements). The controller receives events via handleExtensionEvent, which routes local handlers to the markup_editor_menu controller and dispatches global events through the app event handler.

const myMarkupContextMenu = {
  id: "dita_author_view_menu",
  contextMenuWidget: "markup_editor_menu",
  view: {
    items: [
      {
        displayName: "Edit Cross Reference",
        data: { eventid: "editCrossReference" },
        icon: "link",
        target: {
          key: "displayName",
          value: "Cut",
          viewState: "prepend",
        },
      },
      {
        displayName: "Remove Cross Reference",
        data: { eventid: "removeCrossReference" },
        icon: "deleteOutline",
        target: {
          key: "displayName",
          value: "Edit Cross Reference",
          viewState: "append",
        },
      },
    ],
  },
  controller: {
    editCrossReference() {
      // Use guides.editor APIs to read context
      const ancestorXpaths = guides.editor.runUtil("getAncestorXpaths", true);
      // open dialog or take action...
    },
    removeCrossReference() {
      guides.editor.runCommand("unwrapNode");
    },
  },
};

Utility libraries

  • guides.util.lodash:The lodash utility library, same instance bundled with the editor.

    code language-js
    const uniqueIds = guides.util.lodash.uniq(ids);
    
  • guides.util.async: Async utility library for extension use.

    code language-js
    guides.util.async.parallel([task1, task2], callback);
    

Complete API reference

API
Description
filePath
Get the active document’s file path
version
Get the loaded editor version string
appState(key)
Read a value from the application model
focus()
Focus the active editor
canInsertXmlElement(tag, insertAfter?)
Check if an element can be inserted at cursor
selectCurrentBlockElement()
Select the current block element
getValidElementNamesForInsertion(args)
List valid element names for insertion
updateAttributeByXpath(args)
Update an attribute by XPath
runCommand(name, ...args)
Execute a named editor command
canRunCommand(name, ...args)
Check if a command can execute
runUtil(name, ...args)
Invoke a named editor utility
addDecoration(id, selector, options)
Add or replace a named decoration rule on matching elements
removeDecoration(id)
Remove a decoration rule by ID
batchDecorations(changes)
Apply multiple add/remove decoration changes in one dispatch
clearDecorations()
Remove all active decoration rules
getDecorations()
Get IDs of all currently active decorations
registerPlugin(factory)
Register a ProseMirror plugin factory (also the mechanism for shadow DOM CSS injection)
prosemirror.state
prosemirror-state package
prosemirror.model
prosemirror-model package
prosemirror.view
prosemirror-view package
prosemirror.transform
prosemirror-transform package
prosemirror.commands
prosemirror-commands package
prosemirror.keymap
prosemirror-keymap package
prosemirror.history
prosemirror-history package
prosemirror.tables
prosemirror-tables package
prosemirror.dropcursor
prosemirror-dropcursor package
prosemirror.collab
prosemirror-collab package
prosemirror.markdown
prosemirror-markdown package
recommendation-more-help
experience-manager-guides-help-product-guide