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 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 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];
    toggleHeading: [attrs?: HeadingAttrs | undefined];
    dedentList: [options?: DedentListOptions];
    indentList: [options?: IndentListOptions];
    moveList: [direction: "up" | "down"];
    splitList: [];
    toggleCollapsed: [options?: ToggleCollapsedOptions];
    ... 55 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
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v20.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v20.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v20.11.1/lib/console.js)
.logConsole.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v20.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
(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
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
.createElementDocument.createElement<"div">(tagName: "div", options?: ElementCreationOptions): HTMLDivElement (+2 overloads)
In an HTML document, the **`document.createElement()`** method creates the HTML element specified by `localName`, or an HTMLUnknownElement if `localName` isn't recognized. [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: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)
[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>