Skip to content
GitHubDiscord

Running on Node.js

ProseKit can run in Node.js environments for background processing, CLI applications, and Server-Side Rendering (SSR). The primary limitation is that DOM-dependent APIs are unavailable unless you provide a DOM implementation.

You can create an editor and execute commands just like in a browser environment:

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 defineDropCursor } - {@link defineGapCursor } - {@link defineVirtualSelection } - {@link defineModClickPrevention }
@public
} from 'prosekit/basic'
import { createEditorfunction createEditor<E extends Extension>(options: EditorOptions<E>): Editor<E>
@public
} from 'prosekit/core'
const extensionconst extension: BasicExtension = 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 defineDropCursor } - {@link defineGapCursor } - {@link defineVirtualSelection } - {@link defineModClickPrevention }
@public
()
const editorconst editor: Editor<BasicExtension> = createEditorcreateEditor<BasicExtension>(options: EditorOptions<BasicExtension>): Editor<BasicExtension>
@public
({ extensionEditorOptions<BasicExtension>.extension: BasicExtension
The extension to use when creating the editor.
})
// Set content with JSON editorconst editor: Editor<BasicExtension>.setContentEditor<BasicExtension>.setContent: (content: Node | NodeJSON | string | HTMLElement, selection?: SelectionJSON | Selection | "start" | "end") => void
Update the editor's document and selection.
@paramcontent - The new document to set. It can be one of the following: - A ProseMirror node instance - A ProseMirror node JSON object - An HTML string - An HTML element instance@paramselection - Optional. Specifies the new selection. It can be one of the following: - A ProseMirror selection instance - A ProseMirror selection JSON object - The string "start" (to set selection at the beginning, default value) - The string "end" (to set selection at the end)
({
typetype: string: 'doc', content
content: {
    type: string;
    content: {
        type: string;
        text: string;
    }[];
}[]
: [
{ typeNodeJSON.type: string: 'paragraph', contentNodeJSON.content?: NodeJSON[] | undefined: [{ typeNodeJSON.type: string: 'text', textNodeJSON.text?: string | undefined: 'Hello, Node.js!' }] }, ], }) // Run commands editorconst editor: Editor<BasicExtension>.commands
Editor<BasicExtension>.commands: ToCommandAction<{
    setParagraph: [];
    setHeading: [attrs?: HeadingAttrs | undefined];
    insertHeading: [attrs?: HeadingAttrs | undefined];
    ... 59 more ...;
    redo: [];
}>
All {@link CommandAction } s defined by the editor.
.insertImage
insertImage: CommandAction
(attrs?: ImageAttrs | undefined) => boolean
Execute the current command. Return `true` if the command was successfully executed, otherwise `false`.
({
srcImageAttrs.src?: string | null | undefined: 'https://example.com/logo.png', widthImageAttrs.width?: number | null | undefined: 120, heightImageAttrs.height?: number | null | undefined: 60, }) // Get the document as JSON const jsonconst json: NodeJSON = editorconst editor: Editor<BasicExtension>.getDocJSONEditor<BasicExtension>.getDocJSON: () => NodeJSON
Return a JSON object representing the editor's current document.
()
consolevar console: Console.logConsole.log(...data: any[]): void
[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
(jsonconst json: NodeJSON)

In a standard Node.js environment there is no DOM, so methods requiring DOM access will throw errors:

// ❌ Mounting fails without DOM
editorconst editor: Editor<BasicExtension>.mountEditor<BasicExtension>.mount: (place: HTMLElement | null | undefined) => void
Mount the editor to the given HTML element. Pass `null` or `undefined` to unmount the editor.
(documentvar document: Document
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
.createElementDocument.createElement<"div">(tagName: "div", options?: ElementCreationOptions): HTMLDivElement (+2 overloads)
Creates an instance of the element for the specified tag.
@paramtagName The name of an element. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/createElement)
('div'))
// ❌ HTML serialization requires DOM APIs editorconst editor: Editor<BasicExtension>.getDocHTMLEditor<BasicExtension>.getDocHTML: (options?: getDocHTMLOptions) => string
Return a HTML string representing the editor's current document.
()
// ❌ HTML parsing requires DOM APIs editorconst editor: Editor<BasicExtension>.setContentEditor<BasicExtension>.setContent: (content: Node | NodeJSON | string | HTMLElement, selection?: SelectionJSON | Selection | "start" | "end") => void
Update the editor's document and selection.
@paramcontent - The new document to set. It can be one of the following: - A ProseMirror node instance - A ProseMirror node JSON object - An HTML string - An HTML element instance@paramselection - Optional. Specifies the new selection. It can be one of the following: - A ProseMirror selection instance - A ProseMirror selection JSON object - The string "start" (to set selection at the beginning, default value) - The string "end" (to set selection at the end)
('<p>HTML input</p>')

Two strategies can address the absence of DOM APIs in server environments.

The simplest approach for server-side rendering is client-side pre-rendering: allow the browser to convert documents to HTML and store the result.

// Browser code
const htmlconst html: string = editorconst editor: Editor<BasicExtension>.getDocHTMLEditor<BasicExtension>.getDocHTML: (options?: getDocHTMLOptions) => string
Return a HTML string representing the editor's current document.
()
await fetchfunction fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)
('/api/save', { methodRequestInit.method?: string | undefined
A string to set request's method.
: 'POST', bodyRequestInit.body?: BodyInit | null | undefined
A BodyInit object or null to set request's body.
: htmlconst html: string })

The backend can then serve this static HTML without requiring DOM access.

SSR strategy 2: Server-side DOM simulation

Section titled “SSR strategy 2: Server-side DOM simulation”

For JSON-to-HTML conversion within Node.js, use a headless DOM library such as jsdom or happy-dom.

import { JSDOM } from 'jsdom'

// Initialize virtual DOM
const dom = new JSDOM('')
const document = dom.window.document

editor.setContent({
  type: 'doc',
  content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Foo' }] }],
})
editor.commands.insertText({ text: 'Bar' })

// Provide the document to getDocHTML
const html = editor.getDocHTML({ document })
console.log(html) // => <div><p>BarFoo</p></div>
import { Window } from 'happy-dom'

// Initialize virtual DOM
const window = new Window()
const document = window.document

editor.setContent({
  type: 'doc',
  content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Foo' }] }],
})
editor.commands.insertText({ text: 'Bar' })

// Provide the document to getDocHTML
const html = editor.getDocHTML({ document })
console.log(html) // => <div><p>BarFoo</p></div>