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.
Nodes vs. marks
Section titled “Nodes vs. marks”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 nodeNames const nodeNames: string[] = Object var Object: ObjectConstructorProvides functionality common to all JavaScript objects. .keys ObjectConstructor.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. (editor const editor: Editor<BasicExtension> .schema Editor<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 markNames const markNames: string[] = Object var Object: ObjectConstructorProvides functionality common to all JavaScript objects. .keys ObjectConstructor.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. (editor const editor: Editor<BasicExtension> .schema Editor<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. )Defining a node
Section titled “Defining a node”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]
},
})
``` ({
name NodeSpecOptions<"fancyParagraph", Attrs>.name: "fancyParagraph"The name of the node type. : 'fancyParagraph',
content NodeSpec.content?: string | undefinedThe 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*',
group NodeSpec.group?: string | undefinedThe group or space-separated groups to which this node belongs,
which can be referred to in the content expressions for the
schema. : 'block',
parseDOM NodeSpec.parseDOM?: readonly TagParseRule[] | undefinedAssociates 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. : [{ tag TagParseRule.tag: stringA CSS selector describing the kind of DOM elements to match. : 'p.fancy' }],
toDOM NodeSpec.toDOM?: ((node: Node) => DOMOutputSpec) | undefinedDefines 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', { class class: 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
Defining a mark
Section titled “Defining a mark”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]
},
})
``` ({
name MarkSpecOptions<"highlight", Attrs>.name: "highlight"The name of the mark type. : 'highlight',
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. : 'mark' }],
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. () {
return ['mark', 0]
},
})Adding attributes to existing nodes/marks
Section titled “Adding attributes to existing nodes/marks”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 ({
type NodeAttrOptions<"paragraph", "id", string | null>.type: "paragraph"The name of the node type. : 'paragraph',
attr NodeAttrOptions<"paragraph", "id", string | null>.attr: "id"The name of the attribute. : 'id',
default default?: string | null | undefinedThe 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,
parseDOM NodeAttrOptions<"paragraph", "id", string | null>.parseDOM?: ((node: HTMLElement) => string | null) | undefinedParses the attribute value from the DOM. : (node node: HTMLElement ) => node node: HTMLElement .getAttribute Element.getAttribute(qualifiedName: string): string | nullThe **`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'),
toDOM NodeAttrOptions<"paragraph", "id", string | null>.toDOM?: ((value: string | null) => [key: string, value: string] | null | undefined) | undefinedReturns 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. : (value value: string | null ) => value value: 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. (value value: string )] : null,
})Type inference
Section titled “Type inference”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 { createEditor function createEditor<E extends Extension>(options: EditorOptions<E>): Editor<E>@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'
import { defineBold function defineBold(): BoldExtension@public } from 'prosekit/extensions/bold'
import { defineDoc function defineDoc(): DocExtension@public } from 'prosekit/extensions/doc'
import { defineParagraph function 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 { defineText function defineText(): TextExtension@public } from 'prosekit/extensions/text'
const extension const extension: Union<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]> = union union<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 (
defineDoc function defineDoc(): DocExtension@public (),
defineText function defineText(): TextExtension@public (),
defineParagraph function 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. (),
defineBold function defineBold(): BoldExtension@public (),
)
const editor const editor: Editor<Union<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]>> = createEditor createEditor<Union<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]>>(options: EditorOptions<Union<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]>>): Editor<Union<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]>>@public ({ extension EditorOptions<Union<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]>>.extension: Union<readonly [DocExtension, TextExtension, ParagraphExtension, BoldExtension]>The extension to use when creating the editor. })
editor const 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. ()
editor const 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 registeredSee also
Section titled “See also”- Extensions
- Views: how nodes/marks render to the DOM.
prosekit/corereference- ProseMirror's own schema guide is still the best reference for content expressions and parse rules.