Skip to content

Custom extensions

Most editor features in ProseKit are themselves extensions. When the official ones don't fit, you write your own and union them in.

GoalReach for
Bind a keydefineKeymap
Run a commanddefineCommands
Listen to eventsdefineKeyDownHandler, definePasteHandler, …
Add a node or markdefineNodeSpec, defineMarkSpec
Add an attribute to an existing nodedefineNodeAttr
Drop in a raw ProseMirror plugindefinePlugin
Render a node with a framework componentCustom node views

Most of those are one-call extensions. You only need a "full" custom extension when you're combining several of them.

The official extensions follow a consistent shape. Here's what defineBold looks like, simplified:

import { defineCommands
function defineCommands<T extends Record<string, CommandCreator> = Record<string, CommandCreator>>(commands: T): Extension<{
    Commands: { [K in keyof T]: Parameters<T[K]>; };
}>
, defineKeymapfunction defineKeymap(keymap: Keymap): PlainExtension
Adds a set of keybindings to the editor. Please read the [documentation](https://prosemirror.net/docs/ref/#keymap) for more details.
@public
, defineMarkSpec
function defineMarkSpec<Mark extends string, Attrs extends AnyAttrs = Attrs>(options: MarkSpecOptions<Mark, Attrs>): Extension<{
    Marks: { [K in Mark]: Attrs; };
}>
Defines a mark type into the editor schema.
@public@example```ts const extension = defineMarkSpec({ name: 'bold', parseDOM: [ { tag: 'strong' }, { tag: 'b' }, ], toDOM() { return ['strong', 0] }, }) ```
, unionfunction union<const E extends readonly Extension[]>(...exts: E): Union<E> (+1 overload)
Merges multiple extensions into one. You can pass multiple extensions as arguments or a single array containing multiple extensions.
@throwsIf no extensions are provided.@example```ts function defineFancyNodes() { return union( defineFancyParagraph(), defineFancyHeading(), ) } ```@example```ts function defineFancyNodes() { return union([ defineFancyParagraph(), defineFancyHeading(), ]) } ```@public
, type Extensioninterface Extension<T extends ExtensionTyping<any, any, any> = ExtensionTyping<any, any, any>>
@public
, type Union
type Union<E extends readonly Extension[]> = Extension<{
    Nodes: ExtractNodes<E[number]>;
    Marks: ExtractMarks<E[number]>;
    Commands: ExtractCommands<E[number]>;
}>
@internal
} from 'prosekit/core'
import { toggleMarkfunction toggleMark({ type, attrs, removeWhenPresent, enterInlineAtoms, }: ToggleMarkOptions): Command
Returns a command that toggles the given mark with the given attributes.
@paramoptions@public
} from 'prosekit/core'
const myBoldSpecconst myBoldSpec: Extension<ExtensionTyping<any, any, any>>: Extensioninterface Extension<T extends ExtensionTyping<any, any, any> = ExtensionTyping<any, any, any>>
@public
= defineMarkSpec
defineMarkSpec<"bold", Attrs>(options: MarkSpecOptions<"bold", Attrs>): Extension<{
    Marks: {
        bold: Attrs;
    };
}>
Defines a mark type into the editor schema.
@public@example```ts const extension = defineMarkSpec({ name: 'bold', parseDOM: [ { tag: 'strong' }, { tag: 'b' }, ], toDOM() { return ['strong', 0] }, }) ```
({
nameMarkSpecOptions<"bold", Attrs>.name: "bold"
The name of the mark type.
: 'bold',
parseDOMMarkSpec.parseDOM?: readonly ParseRule[] | undefined
Associates DOM parser information with this mark (see the corresponding [node spec field](https://prosemirror.net/docs/ref/#model.NodeSpec.parseDOM)). The `mark` field in the rules is implied.
: [{ tagTagParseRule.tag: string
A CSS selector describing the kind of DOM elements to match.
: 'strong' }, { tagTagParseRule.tag: string
A CSS selector describing the kind of DOM elements to match.
: 'b' }],
toDOMMarkSpec.toDOM?: ((mark: Mark, inline: boolean) => DOMOutputSpec) | undefined
Defines the default way marks of this type should be serialized to DOM/HTML. When the resulting spec contains a hole, that is where the marked content is placed. Otherwise, it is appended to the top node.
: () => ['strong', 0],
}) const myBoldCommands
const myBoldCommands: Extension<{
    Commands: {
        toggleBold: [];
    };
}>
= defineCommands
defineCommands<{
    toggleBold: () => Command;
}>(commands: {
    toggleBold: () => Command;
}): Extension<{
    Commands: {
        toggleBold: [];
    };
}>
({
toggleBoldtoggleBold: () => Command: () => toggleMarkfunction toggleMark({ type, attrs, removeWhenPresent, enterInlineAtoms, }: ToggleMarkOptions): Command
Returns a command that toggles the given mark with the given attributes.
@paramoptions@public
({ typeToggleMarkOptions.type: string | MarkType
The mark type to toggle.
: 'bold' }),
}) const myBoldKeymapconst myBoldKeymap: PlainExtension = defineKeymapfunction defineKeymap(keymap: Keymap): PlainExtension
Adds a set of keybindings to the editor. Please read the [documentation](https://prosemirror.net/docs/ref/#keymap) for more details.
@public
({
'Mod-b': toggleMarkfunction toggleMark({ type, attrs, removeWhenPresent, enterInlineAtoms, }: ToggleMarkOptions): Command
Returns a command that toggles the given mark with the given attributes.
@paramoptions@public
({ typeToggleMarkOptions.type: string | MarkType
The mark type to toggle.
: 'bold' }),
}) export function defineMyBold
function defineMyBold(): Union<readonly [Extension<ExtensionTyping<any, any, any>>, Extension<{
    Commands: {
        toggleBold: [];
    };
}>, PlainExtension]>
() {
return union
union<readonly [Extension<ExtensionTyping<any, any, any>>, Extension<{
    Commands: {
        toggleBold: [];
    };
}>, PlainExtension]>(exts_0: Extension<ExtensionTyping<any, any, any>>, exts_1: Extension<{
    Commands: {
        toggleBold: [];
    };
}>, exts_2: PlainExtension): Union<readonly [Extension<ExtensionTyping<any, any, any>>, Extension<{
    Commands: {
        toggleBold: [];
    };
}>, PlainExtension]> (+1 overload)
Merges multiple extensions into one. You can pass multiple extensions as arguments or a single array containing multiple extensions.
@throwsIf no extensions are provided.@example```ts function defineFancyNodes() { return union( defineFancyParagraph(), defineFancyHeading(), ) } ```@example```ts function defineFancyNodes() { return union([ defineFancyParagraph(), defineFancyHeading(), ]) } ```@public
(
myBoldSpecconst myBoldSpec: Extension<ExtensionTyping<any, any, any>>, myBoldCommands
const myBoldCommands: Extension<{
    Commands: {
        toggleBold: [];
    };
}>
,
myBoldKeymapconst myBoldKeymap: PlainExtension, ) }

Three building blocks (spec, commands, keymap) joined by union. Same pattern works for any feature: define each piece in isolation, then compose. See Concepts → Extensions for the full story.

import { defineBasicExtensionfunction defineBasicExtension(): BasicExtension
Define a basic extension that includes some common functionality. You can copy this function and customize it to your needs. It's a combination of the following extension functions: - {@link defineDoc } - {@link defineText } - {@link defineParagraph } - {@link defineHeading } - {@link defineList } - {@link defineBlockquote } - {@link defineImage } - {@link defineHorizontalRule } - {@link defineHardBreak } - {@link defineTable } - {@link defineCodeBlock } - {@link defineItalic } - {@link defineBold } - {@link defineUnderline } - {@link defineStrike } - {@link defineCode } - {@link defineLink } - {@link defineBaseKeymap } - {@link defineBaseCommands } - {@link defineHistory } - {@link defineGapCursor } - {@link defineVirtualSelection } - {@link defineModClickPrevention }
@public
} from 'prosekit/basic'
import { createEditorfunction createEditor<E extends Extension>(options: EditorOptions<E>): Editor<E>
@public
, defineKeymapfunction defineKeymap(keymap: Keymap): PlainExtension
Adds a set of keybindings to the editor. Please read the [documentation](https://prosemirror.net/docs/ref/#keymap) for more details.
@public
, unionfunction union<const E extends readonly Extension[]>(...exts: E): Union<E> (+1 overload)
Merges multiple extensions into one. You can pass multiple extensions as arguments or a single array containing multiple extensions.
@throwsIf no extensions are provided.@example```ts function defineFancyNodes() { return union( defineFancyParagraph(), defineFancyHeading(), ) } ```@example```ts function defineFancyNodes() { return union([ defineFancyParagraph(), defineFancyHeading(), ]) } ```@public
} from 'prosekit/core'
const myKeymapconst myKeymap: PlainExtension = defineKeymapfunction defineKeymap(keymap: Keymap): PlainExtension
Adds a set of keybindings to the editor. Please read the [documentation](https://prosemirror.net/docs/ref/#keymap) for more details.
@public
({
'Mod-Shift-x': () => { consolevar console: Console.logConsole.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
('custom shortcut')
return true }, }) const editorconst editor: Editor<Union<readonly [BasicExtension, PlainExtension]>> = createEditorcreateEditor<Union<readonly [BasicExtension, PlainExtension]>>(options: EditorOptions<Union<readonly [BasicExtension, PlainExtension]>>): Editor<Union<readonly [BasicExtension, PlainExtension]>>
@public
({
extensionEditorOptions<Union<readonly [BasicExtension, PlainExtension]>>.extension: Union<readonly [BasicExtension, PlainExtension]>
The extension to use when creating the editor.
: unionunion<readonly [BasicExtension, PlainExtension]>(exts_0: BasicExtension, exts_1: PlainExtension): Union<readonly [BasicExtension, PlainExtension]> (+1 overload)
Merges multiple extensions into one. You can pass multiple extensions as arguments or a single array containing multiple extensions.
@throwsIf no extensions are provided.@example```ts function defineFancyNodes() { return union( defineFancyParagraph(), defineFancyHeading(), ) } ```@example```ts function defineFancyNodes() { return union([ defineFancyParagraph(), defineFancyHeading(), ]) } ```@public
(defineBasicExtensionfunction defineBasicExtension(): BasicExtension
Define a basic extension that includes some common functionality. You can copy this function and customize it to your needs. It's a combination of the following extension functions: - {@link defineDoc } - {@link defineText } - {@link defineParagraph } - {@link defineHeading } - {@link defineList } - {@link defineBlockquote } - {@link defineImage } - {@link defineHorizontalRule } - {@link defineHardBreak } - {@link defineTable } - {@link defineCodeBlock } - {@link defineItalic } - {@link defineBold } - {@link defineUnderline } - {@link defineStrike } - {@link defineCode } - {@link defineLink } - {@link defineBaseKeymap } - {@link defineBaseCommands } - {@link defineHistory } - {@link defineGapCursor } - {@link defineVirtualSelection } - {@link defineModClickPrevention }
@public
(), myKeymapconst myKeymap: PlainExtension),
})

Or, after the editor exists, hot-add it with editor.use(extension) (see Concepts → The Editor).