Skip to content

Schema

A ProseMirror schema describes the shape of valid documents. In ProseKit you don't construct it directly. Every extension contributes its piece, and the editor merges them.

A document is a tree of nodes. Block nodes (paragraphs, headings, lists) hold other nodes; inline nodes (text, images, hard breaks) live inside blocks. Marks are inline annotations on text: bold, italic, links, code.

// Inspect the schema produced by your extensions:
const nodeNamesconst nodeNames: string[] = Objectvar Object: ObjectConstructor
Provides functionality common to all JavaScript objects.
.keysObjectConstructor.keys(o: {}): string[] (+1 overload)
Returns the names of the enumerable string properties and methods of an object.
@paramo Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.
(editorconst editor: Editor<BasicExtension>.schemaEditor<BasicExtension>.schema: Schema<"doc" | "text" | "paragraph" | "heading" | "list" | "blockquote" | "image" | "horizontalRule" | "hardBreak" | "table" | "tableRow" | "tableCell" | "tableHeaderCell" | "codeBlock", "italic" | "bold" | "underline" | "strike" | "code" | "link">
The editor schema.
.nodes
Schema<"doc" | "text" | "paragraph" | "heading" | "list" | "blockquote" | "image" | "horizontalRule" | "hardBreak" | "table" | "tableRow" | "tableCell" | "tableHeaderCell" | "codeBlock", "italic" | ... 4 more ... | "link">.nodes: {
    readonly doc: NodeType;
    readonly text: NodeType;
    readonly paragraph: NodeType;
    readonly heading: NodeType;
    readonly list: NodeType;
    readonly blockquote: NodeType;
    readonly image: NodeType;
    readonly horizontalRule: NodeType;
    readonly hardBreak: NodeType;
    readonly table: NodeType;
    readonly tableRow: NodeType;
    readonly tableCell: NodeType;
    readonly tableHeaderCell: NodeType;
    readonly codeBlock: NodeType;
} & {
    ...;
}
An object mapping the schema's node names to node type objects.
)
const markNamesconst markNames: string[] = Objectvar Object: ObjectConstructor
Provides functionality common to all JavaScript objects.
.keysObjectConstructor.keys(o: {}): string[] (+1 overload)
Returns the names of the enumerable string properties and methods of an object.
@paramo Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.
(editorconst editor: Editor<BasicExtension>.schemaEditor<BasicExtension>.schema: Schema<"doc" | "text" | "paragraph" | "heading" | "list" | "blockquote" | "image" | "horizontalRule" | "hardBreak" | "table" | "tableRow" | "tableCell" | "tableHeaderCell" | "codeBlock", "italic" | "bold" | "underline" | "strike" | "code" | "link">
The editor schema.
.marks
Schema<"doc" | "text" | "paragraph" | "heading" | "list" | "blockquote" | "image" | "horizontalRule" | "hardBreak" | "table" | "tableRow" | "tableCell" | "tableHeaderCell" | "codeBlock", "italic" | ... 4 more ... | "link">.marks: {
    readonly italic: MarkType;
    readonly bold: MarkType;
    readonly underline: MarkType;
    readonly strike: MarkType;
    readonly code: MarkType;
    readonly link: MarkType;
} & {
    readonly [key: string]: MarkType;
}
A map from mark names to mark type objects.
)

Use defineNodeSpec to add a node type. The name, content, group, parseDOM, and toDOM fields follow ProseMirror conventions exactly. ProseKit just adds typed attribute support.

import { defineNodeSpec
function defineNodeSpec<Node extends string, Attrs extends AnyAttrs = Attrs>(options: NodeSpecOptions<Node, Attrs>): Extension<{
    Nodes: { [K in Node]: Attrs; };
}>
Defines a node type into the editor schema.
@public@example```ts const extension = defineNodeSpec({ name: 'fancyParagraph', content: 'inline*', group: 'block', parseDOM: [{ tag: 'p.fancy' }], toDOM() { return ['p', { 'class': 'fancy' }, 0] }, }) ```
} from 'prosekit/core'
const fancyParagraph
const fancyParagraph: Extension<{
    Nodes: {
        fancyParagraph: Attrs;
    };
}>
= defineNodeSpec
defineNodeSpec<"fancyParagraph", Attrs>(options: NodeSpecOptions<"fancyParagraph", Attrs>): Extension<{
    Nodes: {
        fancyParagraph: Attrs;
    };
}>
Defines a node type into the editor schema.
@public@example```ts const extension = defineNodeSpec({ name: 'fancyParagraph', content: 'inline*', group: 'block', parseDOM: [{ tag: 'p.fancy' }], toDOM() { return ['p', { 'class': 'fancy' }, 0] }, }) ```
({
nameNodeSpecOptions<"fancyParagraph", Attrs>.name: "fancyParagraph"
The name of the node type.
: 'fancyParagraph',
contentNodeSpec.content?: string | undefined
The content expression for this node, as described in the [schema guide](https://prosemirror.net/docs/guide/#schema.content_expressions). When not given, the node does not allow any content.
: 'inline*',
groupNodeSpec.group?: string | undefined
The group or space-separated groups to which this node belongs, which can be referred to in the content expressions for the schema.
: 'block',
parseDOMNodeSpec.parseDOM?: readonly TagParseRule[] | undefined
Associates DOM parser information with this node, which can be used by [`DOMParser.fromSchema`](https://prosemirror.net/docs/ref/#model.DOMParser^fromSchema) to automatically derive a parser. The `node` field in the rules is implied (the name of this node will be filled in automatically). If you supply your own parser, you do not need to also specify parsing rules in your schema.
: [{ tagTagParseRule.tag: string
A CSS selector describing the kind of DOM elements to match.
: 'p.fancy' }],
toDOMNodeSpec.toDOM?: ((node: Node) => DOMOutputSpec) | undefined
Defines the default way a node of this type should be serialized to DOM/HTML (as used by [`DOMSerializer.fromSchema`](https://prosemirror.net/docs/ref/#model.DOMSerializer^fromSchema)). Should return a DOM node or an [array structure](https://prosemirror.net/docs/ref/#model.DOMOutputSpec) that describes one, with an optional number zero (“hole”) in it to indicate where the node's content should be inserted. For text nodes, the default is to create a text DOM node. Though it is possible to create a serializer where text is rendered differently, this is not supported inside the editor, so you shouldn't override that in your text node spec.
() {
return ['p', { classclass: string: 'fancy' }, 0] }, })

content is a content expression:

  • "inline*": zero or more inline nodes
  • "block+": one or more block nodes
  • "text*": zero or more text nodes

defineMarkSpec is the mark equivalent. You can borrow the existing extensions for examples. The Bold page links to the source of defineBoldSpec.

import { 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] }, }) ```
} from 'prosekit/core'
const highlight
const highlight: Extension<{
    Marks: {
        highlight: Attrs;
    };
}>
= defineMarkSpec
defineMarkSpec<"highlight", Attrs>(options: MarkSpecOptions<"highlight", Attrs>): Extension<{
    Marks: {
        highlight: 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<"highlight", Attrs>.name: "highlight"
The name of the mark type.
: 'highlight',
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.
: 'mark' }],
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.
() {
return ['mark', 0] }, })

defineNodeAttr and defineMarkAttr extend an existing type without redefining it. Useful for adding optional metadata (e.g., an id on every paragraph) that another extension is the source of truth for.

import { defineNodeAttr
function defineNodeAttr<NodeType extends string = string, AttrName extends string = string, AttrType = any>(options: NodeAttrOptions<NodeType, AttrName, AttrType>): Extension<{
    Nodes: { [K in NodeType]: { [K in AttrName]: AttrType; }; };
}>
Defines an attribute for a node type.
@public
} from 'prosekit/core'
const paragraphId
const paragraphId: Extension<{
    Nodes: {
        paragraph: {
            id: string | null;
        };
    };
}>
= defineNodeAttr
defineNodeAttr<"paragraph", "id", string | null>(options: NodeAttrOptions<"paragraph", "id", string | null>): Extension<{
    Nodes: {
        paragraph: {
            id: string | null;
        };
    };
}>
Defines an attribute for a node type.
@public
({
typeNodeAttrOptions<"paragraph", "id", string | null>.type: "paragraph"
The name of the node type.
: 'paragraph',
attrNodeAttrOptions<"paragraph", "id", string | null>.attr: "id"
The name of the attribute.
: 'id',
defaultdefault?: string | null | undefined
The default value for this attribute, to use when no explicit value is provided. Attributes that have no default must be provided whenever a node or mark of a type that has them is created.
: null,
parseDOMNodeAttrOptions<"paragraph", "id", string | null>.parseDOM?: ((node: HTMLElement) => string | null) | undefined
Parses the attribute value from the DOM.
: (nodenode: HTMLElement) => nodenode: HTMLElement.getAttributeElement.getAttribute(qualifiedName: string): string | null
The **`getAttribute()`** method of the Element interface returns the value of a specified attribute on the element. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getAttribute)
('id'),
toDOMNodeAttrOptions<"paragraph", "id", string | null>.toDOM?: ((value: string | null) => [key: string, value: string] | null | undefined) | undefined
Returns the attribute key and value to be set on the HTML element. If the returned `key` is `"style"`, the value is a string of CSS properties and will be prepended to the existing `style` attribute on the DOM node.
@paramvalue - The value of the attribute of current ProseMirror node.
: (valuevalue: string | null) => valuevalue: string | null ? ['id', String
var String: StringConstructor
(value?: any) => string
Allows manipulation and formatting of text strings and determination and location of substrings within strings.
(valuevalue: string)] : null,
})

ProseKit threads the union of every node, mark, and command through the TypeScript type system. The Editor instance ends up with nodes, marks, and commands keyed by exactly what you registered.

import { createEditorfunction createEditor<E extends Extension>(options: EditorOptions<E>): Editor<E>
@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'
import { defineBoldfunction defineBold(): BoldExtension
@public
} from 'prosekit/extensions/bold'
import { defineDocfunction defineDoc(): DocExtension
@public
} from 'prosekit/extensions/doc'
import { defineParagraphfunction defineParagraph(): ParagraphExtension
@publicDefines a paragraph node. The paragraph node spec has the highest priority, because it should be the default block node for most cases.
} from 'prosekit/extensions/paragraph'
import { defineTextfunction defineText(): TextExtension
@public
} from 'prosekit/extensions/text'
const extensionconst extension: Union<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]> = unionunion<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]>(exts_0: DocExtension, exts_1: TextExtension, exts_2: ParagraphExtension, exts_3: BoldExtension): Union<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]> (+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
(
defineDocfunction defineDoc(): DocExtension
@public
(),
defineTextfunction defineText(): TextExtension
@public
(),
defineParagraphfunction defineParagraph(): ParagraphExtension
@publicDefines a paragraph node. The paragraph node spec has the highest priority, because it should be the default block node for most cases.
(),
defineBoldfunction defineBold(): BoldExtension
@public
(),
) const editorconst editor: Editor<Union<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]>> = createEditorcreateEditor<Union<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]>>(options: EditorOptions<Union<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]>>): Editor<Union<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]>>
@public
({ extensionEditorOptions<Union<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]>>.extension: Union<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]>
The extension to use when creating the editor.
})
editorconst editor: Editor<Union<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]>>.marks
Editor<Union<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]>>.marks: ToMarkAction<SimplifyDeeper<{
    bold: {
        readonly [x: string]: any;
    };
}>>
All {@link MarkAction } s defined by the editor.
.bold
bold: MarkAction<{
    readonly [x: string]: any;
}>
.isActive
MarkAction<{ readonly [x: string]: any; }>.isActive: (attrs?: {
    readonly [x: string]: any;
} | undefined) => boolean
Checks if the mark is active in the current editor selection. If the optional `attrs` parameter is provided, it will check if the mark is active with the given attributes.
()
editorconst editor: Editor<Union<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]>>.commands
Editor<Union<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]>>.commands: ToCommandAction<{
    toggleBold: [];
    setParagraph: [];
}>
All {@link CommandAction } s defined by the editor.
.toggleBold
toggleBold: CommandAction
() => boolean
Execute the current command. Return `true` if the command was successfully executed, otherwise `false`.
()
// editor.commands.toggleItalic() // TS error: italic was not registered