Documentation Index
Fetch the complete documentation index at: https://docs.superdoc.dev/llms.txt
Use this file to discover all available pages before exploring further.
Custom extensions are an advanced surface. For most app-level customization, the Document API handles document mutations and Custom UI handles toolbar, comments, and review panels — both are stable, framework-friendly, and aligned with SuperDoc’s document model. Reach for custom extensions only when you need editor internals (custom marks, nodes, or chain commands).
Create extensions to add custom features to SuperDoc.
Basic extension
import { Extensions } from 'superdoc';
const { Extension } = Extensions;
const MyExtension = Extension.create({
name: 'myExtension',
addCommands() {
return {
myCommand: () => ({ commands }) => {
console.log('Command executed');
return true;
}
};
}
});
// Use it
editor.commands.myCommand();
Extension types
Node extension
For document elements:
import { Extensions } from 'superdoc';
const { Node } = Extensions;
const CustomBlock = Node.create({
name: 'customBlock',
group: 'block',
content: 'inline*',
parseHTML() {
return [{ tag: 'div[data-custom]' }];
},
renderHTML({ HTMLAttributes }) {
return ['div', { 'data-custom': '' }, 0];
}
});
Mark extension
For inline formatting:
import { Extensions } from 'superdoc';
const { Mark } = Extensions;
const Highlight = Mark.create({
name: 'highlight',
addCommands() {
return {
toggleHighlight: () => ({ commands }) => {
return commands.toggleMark(this.name);
}
};
}
});
Adding features
Commands
addCommands() {
return {
simpleCommand: () => ({ commands }) => {
return commands.insertContent('Hello');
},
complexCommand: (text) => ({ state, dispatch }) => {
dispatch(state.tr.insertText(text));
return true;
}
};
}
Keyboard shortcuts
addKeyboardShortcuts() {
return {
'Mod-Shift-h': () => this.editor.commands.toggleHighlight()
};
}
ProseMirror plugins
Your extension can also define ProseMirror plugins which will let you perform more advanced things, such as listening to browser events attached to a node.
import { Plugin, PluginKey } from 'prosemirror-state';
// ...
addPmPlugins() {
return [
new Plugin({
key: new PluginKey('myPlugin'),
props: {
handleClickOn: (view, pos, node, nodePos, event, direct) => {
console.log("Node was clicked!");
}
},
})
];
}
You can read more about ProseMirror’s plugin system here.
Configuration
const ConfigurableExt = Extension.create({
addOptions() {
return {
color: '#0000FF',
enabled: true
};
},
addCommands() {
return {
applyColor: () => () => {
// Use this.options.color
}
};
}
});
// Configure when using
ConfigurableExt.configure({ color: '#FF0000' });
Type-safe factories
Use defineNode and defineMark instead of Node.create() / Mark.create() for typed attributes:
import { defineNode, defineMark } from 'superdoc/super-editor';
const CustomBlock = defineNode({
name: 'customBlock',
group: 'block',
content: 'inline*',
addAttributes() {
return {
level: { default: 1 },
collapsed: { default: false },
};
},
parseHTML() {
return [{ tag: 'div[data-custom]' }];
},
renderHTML({ HTMLAttributes }) {
return ['div', { 'data-custom': '' }, 0];
},
});
const CustomMark = defineMark({
name: 'customMark',
addAttributes() {
return {
color: { default: null },
};
},
});
With TypeScript generics you get full attribute autocompletion:
interface CustomBlockAttrs {
level: number;
collapsed: boolean;
}
const CustomBlock = defineNode<{}, {}, CustomBlockAttrs>({
name: 'customBlock',
// attrs are now typed as CustomBlockAttrs
});
Type guards
Use type guards when traversing the document to get typed node/mark attributes:
import { isNodeType, assertNodeType, isMarkType } from 'superdoc/super-editor';
// Type guard — narrows the type in if blocks
editor.state.doc.descendants((node) => {
if (isNodeType(node, 'paragraph')) {
// node.attrs is now typed as ParagraphAttrs
console.log(node.attrs.paragraphProperties?.styleId);
}
});
// Assertion — throws if type doesn't match
const node = state.doc.nodeAt(pos);
assertNodeType(node, 'table');
// node.attrs is now typed as TableAttrs
// Mark type guard
for (const mark of node.marks) {
if (isMarkType(mark, 'bold')) {
// mark.attrs is typed
}
}
Using your extension
new SuperDoc({
selector: '#editor',
document: 'document.docx',
editorExtensions: [
MyExtension,
]
});