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.
When you actually need a custom extension
Section titled “When you actually need a custom extension”| Goal | Reach for |
|---|---|
| Bind a key | defineKeymap |
| Run a command | defineCommands |
| Listen to events | defineKeyDownHandler, definePasteHandler, … |
| Add a node or mark | defineNodeSpec, defineMarkSpec |
| Add an attribute to an existing node | defineNodeAttr |
| Drop in a raw ProseMirror plugin | definePlugin |
| Render a node with a framework component | Custom node views |
Most of those are one-call extensions. You only need a "full" custom extension when you're combining several of them.
Anatomy of a typical extension
Section titled “Anatomy of a typical extension”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]>; };
}>
, defineKeymap function defineKeymap(keymap: Keymap): PlainExtensionAdds 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]
},
})
``` , union function 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 Extension interface 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 { toggleMark function toggleMark({ type, attrs, removeWhenPresent, enterInlineAtoms, }: ToggleMarkOptions): CommandReturns a command that toggles the given mark with the given attributes.@paramoptions@public } from 'prosekit/core'
const myBoldSpec const myBoldSpec: Extension<ExtensionTyping<any, any, any>> : Extension interface 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]
},
})
``` ({
name MarkSpecOptions<"bold", Attrs>.name: "bold"The name of the mark type. : 'bold',
parseDOM MarkSpec.parseDOM?: readonly ParseRule[] | undefinedAssociates 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. : [{ tag TagParseRule.tag: stringA CSS selector describing the kind of DOM elements to match. : 'strong' }, { tag TagParseRule.tag: stringA CSS selector describing the kind of DOM elements to match. : 'b' }],
toDOM MarkSpec.toDOM?: ((mark: Mark, inline: boolean) => DOMOutputSpec) | undefinedDefines 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: [];
};
}>
({
toggleBold toggleBold: () => Command : () => toggleMark function toggleMark({ type, attrs, removeWhenPresent, enterInlineAtoms, }: ToggleMarkOptions): CommandReturns a command that toggles the given mark with the given attributes.@paramoptions@public ({ type ToggleMarkOptions.type: string | MarkTypeThe mark type to toggle. : 'bold' }),
})
const myBoldKeymap const myBoldKeymap: PlainExtension = defineKeymap function defineKeymap(keymap: Keymap): PlainExtensionAdds a set of keybindings to the editor. Please read the
[documentation](https://prosemirror.net/docs/ref/#keymap) for more details.@public ({
'Mod-b': toggleMark function toggleMark({ type, attrs, removeWhenPresent, enterInlineAtoms, }: ToggleMarkOptions): CommandReturns a command that toggles the given mark with the given attributes.@paramoptions@public ({ type ToggleMarkOptions.type: string | MarkTypeThe 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 (
myBoldSpec const myBoldSpec: Extension<ExtensionTyping<any, any, any>> ,
myBoldCommands const myBoldCommands: Extension<{
Commands: {
toggleBold: [];
};
}>
,
myBoldKeymap const 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.
Adding to an existing editor
Section titled “Adding to an existing editor”import { defineBasicExtension function defineBasicExtension(): BasicExtensionDefine 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 { createEditor function createEditor<E extends Extension>(options: EditorOptions<E>): Editor<E>@public , defineKeymap function defineKeymap(keymap: Keymap): PlainExtensionAdds a set of keybindings to the editor. Please read the
[documentation](https://prosemirror.net/docs/ref/#keymap) for more details.@public , union function 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 myKeymap const myKeymap: PlainExtension = defineKeymap function defineKeymap(keymap: Keymap): PlainExtensionAdds a set of keybindings to the editor. Please read the
[documentation](https://prosemirror.net/docs/ref/#keymap) for more details.@public ({
'Mod-Shift-x': () => {
console var console: Console .log Console.log(...data: any[]): voidThe **`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 editor const editor: Editor<Union<readonly [BasicExtension, PlainExtension]>> = createEditor createEditor<Union<readonly [BasicExtension, PlainExtension]>>(options: EditorOptions<Union<readonly [BasicExtension, PlainExtension]>>): Editor<Union<readonly [BasicExtension, PlainExtension]>>@public ({
extension EditorOptions<Union<readonly [BasicExtension, PlainExtension]>>.extension: Union<readonly [BasicExtension, PlainExtension]>The extension to use when creating the editor. : union union<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 (defineBasicExtension function defineBasicExtension(): BasicExtensionDefine 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 (), myKeymap const myKeymap: PlainExtension ),
})Or, after the editor exists, hot-add it with editor.use(extension) (see Concepts → The Editor).