From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
  • Introduction
  • Installation
    • Plate UI
      • Next.js
      • React
    • Manual
    • RSC
    • Node.js
    • Local Docs
    • MCP
  • Releases

Node.js

PreviousNext

Install and configure Plate for Node.js.

Use Plate in Node.js when you need to read, validate, transform, or serialize editor values outside the browser. Node scripts use the base runtime imports, while React editors use /react subpaths. This guide walks through a server-safe editor, Markdown IO, and a content transform.

Node.js Setup

Use base imports

Do not import from platejs/react or @platejs/*/react in Node.js scripts. Use createBaseEditor from platejs and base plugins from @platejs/* packages.

RSCLocal Docs

On This Page

Node.js SetupInstall PackagesCreate a Server EditorRead and Write MarkdownTransform ContentRuntime BoundariesAPI ReferenceNext Steps
Build your editor
Production-ready AI template and reusable components.
Get all-access

Install Packages

Install the core runtime and the packages your pipeline needs.

pnpm add platejs @platejs/basic-nodes @platejs/markdown
pnpm add platejs @platejs/basic-nodes @platejs/markdown
PackageOwns
platejscreateBaseEditor, core editor APIs, core paragraph behavior.
@platejs/basic-nodesBase headings, blockquotes, horizontal rules, and text marks.
@platejs/markdownMarkdown serialization, deserialization, and the MarkdownPlugin API.

Create a Server Editor

Create the editor with base plugins only. The editor exposes editor.read(...) for committed state and editor.update(...) for writes without mounting a React tree.

scripts/process-content.ts
import type { Value } from 'platejs';
 
import { BaseBoldPlugin, BaseHeadingPlugin } from '@platejs/basic-nodes';
import { createBaseEditor } from 'platejs';
 
const value: Value = [
  {
    children: [{ text: 'Document Title' }],
    type: 'heading', level: 1,
  },
  {
    children: [
      { text: 'With ' },
      { bold: true, text: 'bold' },
      { text: ' text.' },
    ],
    type: 'paragraph',
  },
];
 
const editor = createBaseEditor({
  plugins: [BaseHeadingPlugin, BaseBoldPlugin],
  initialValue: value,
});
 
const plainText = editor.read.text.string([]);
 
console.info(plainText);
scripts/process-content.ts
import type { Value } from 'platejs';
 
import { BaseBoldPlugin, BaseHeadingPlugin } from '@platejs/basic-nodes';
import { createBaseEditor } from 'platejs';
 
const value: Value = [
  {
    children: [{ text: 'Document Title' }],
    type: 'heading', level: 1,
  },
  {
    children: [
      { text: 'With ' },
      { bold: true, text: 'bold' },
      { text: ' text.' },
    ],
    type: 'paragraph',
  },









Read and Write Markdown

Add MarkdownPlugin when the script needs Markdown conversion. Use the editor's markdown API for both directions.

scripts/markdown-io.ts
import { BaseBoldPlugin, BaseHeadingPlugin } from '@platejs/basic-nodes';
import { MarkdownPlugin } from '@platejs/markdown';
import { createBaseEditor } from 'platejs';
 
const editor = createBaseEditor({
  plugins: [BaseHeadingPlugin, BaseBoldPlugin, MarkdownPlugin],
});
 
const value = editor.api.markdown.deserialize(
  [
    '# Migration Note',
    '',
    'Move legacy content into **Plate** format.',
  ].join('\n')
);
 
const markdown = editor.api.markdown.serialize({ value });
 
console.info(markdown);
scripts/markdown-io.ts
import { BaseBoldPlugin, BaseHeadingPlugin } from '@platejs/basic-nodes';
import { MarkdownPlugin } from '@platejs/markdown';
import { createBaseEditor } from 'platejs';
 
const editor = createBaseEditor({
  plugins: [BaseHeadingPlugin, BaseBoldPlugin, MarkdownPlugin],
});
 
const value = editor.api.markdown.deserialize(
  [
    '# Migration Note',
    '',
    'Move legacy content into **Plate** format.',
  ].join('\n')
);
 
const markdown = editor.api.markdown.serialize({ value });
 

Transform Content

Use transaction groups for migrations and bulk cleanup. Pass at: [] when the operation should scan the whole document.

scripts/normalize-headings.ts
import type { Value } from 'platejs';
 
import {
  BaseBoldPlugin,
  BaseHeadingPlugin,
  
} from '@platejs/basic-nodes';
import { MarkdownPlugin } from '@platejs/markdown';
import { createBaseEditor } from 'platejs';
 
export function normalizeHeadings(value: Value) {
  const editor = createBaseEditor({
    plugins: [BaseHeadingPlugin,  BaseBoldPlugin, MarkdownPlugin],
    initialValue: value,
  });
 
  const insertAt = editor.read((state) => [state.value.root().length]);
 
  editor.update((tx) => {
    tx.nodes.set(
      { type: 'heading', level: 2 },
      {
        at: [],
        match: (node) => 'type' in node && node.type === 'h1',
      }
    );
 
    tx.nodes.insert(
      [{ children: [{ text: 'Imported from the legacy CMS.' }], type: 'paragraph' }],
      { at: insertAt }
    );
  });
 
  return {
    markdown: editor.api.markdown.serialize(),
    text: editor.read((state) => state.text.string([])),
    value: editor.read((state) => state.value.root()),
  };
}
scripts/normalize-headings.ts
import type { Value } from 'platejs';
 
import {
  BaseBoldPlugin,
  BaseHeadingPlugin,
  
} from '@platejs/basic-nodes';
import { MarkdownPlugin } from '@platejs/markdown';
import { createBaseEditor } from 'platejs';
 
export function normalizeHeadings(value: Value) {
  const editor = createBaseEditor({
    plugins: [BaseHeadingPlugin,  BaseBoldPlugin, MarkdownPlugin],
    initialValue: value,
  });
 
  const insertAt = editor.read((state) => [state.value.





















Runtime Boundaries

RuntimeImport fromUse for
Node.js scriptsplatejs, @platejs/*Migration, validation, serialization, search indexing.
React editorsplatejs/react, @platejs/*/reactEditable UI, hooks, rendered components, toolbar behavior.
Static renderingplatejs/staticServer-rendered read-only content.

Plugin packages can expose both base and React entrypoints. In Node.js, choose the base entrypoint even when the same feature has React components for the browser editor.

API Reference

APIPackageNotes
createBaseEditorplatejsCreates a non-React editor instance.
editor.read((state) => state.text.string([]))platejsReads text from the whole document.
editor.update((tx) => tx.nodes.set(...))platejsUpdates matching nodes. Use at: [] for document-wide transforms.
editor.update((tx) => tx.nodes.insert(...))platejsInserts nodes at a path.
editor.api.markdown.deserialize@platejs/markdownConverts Markdown into a Plate value.
editor.api.markdown.serialize@platejs/markdownConverts the editor value or an explicit value option to Markdown.

Next Steps

TaskGuide
Serialize to MarkdownMarkdown
Serialize to HTMLHTML
Render read-only contentStatic Rendering
Query editor stateEditor API
Apply transformsEditor Transforms

Done. You now have a server-safe Plate runtime that can power migration scripts, validation jobs, and content serialization.

];
const editor = createBaseEditor({
plugins: [BaseHeadingPlugin, BaseBoldPlugin],
initialValue: value,
});
const plainText = editor.read.text.string([]);
console.info(plainText);
console.info(markdown);
root
().
length
]);
editor.update((tx) => {
tx.nodes.set(
{ type: 'heading', level: 2 },
{
at: [],
match: (node) => 'type' in node && node.type === 'h1',
}
);
tx.nodes.insert(
[{ children: [{ text: 'Imported from the legacy CMS.' }], type: 'paragraph' }],
{ at: insertAt }
);
});
return {
markdown: editor.api.markdown.serialize(),
text: editor.read((state) => state.text.string([])),
value: editor.read((state) => state.value.root()),
};
}