From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
  • Feature Kits
  • Plugin
    • Plugin Methods
    • Plugin Shortcuts
    • Plugin Context
    • Plugin Components
    • Plugin Rules
    • Editing Behavior
    • Plugin Input Rules
  • Editor
    • Editor Methods
    • Controlled Value
  • Performance
  • Static Rendering
  • HTML
  • Markdown
  • Form
  • TypeScript
  • Debugging
  • Unit Testing
  • Browser
  • Troubleshooting

Editor Methods

PreviousNext

Read, mutate, and configure a Plate editor instance.

The Plate editor exposes two main method surfaces: editor.api for reads and services, and editor.update for commands that change editor state. In React, pick the editor hook by how often the component should re-render.

Access the Editor

NeedUse
Read the required editor inside callbacks.useEditor()
Re-render from an editor-derived value.useEditorSelector(selector, options?)
Re-render from immutable state.
Document ModelControlled Value

On This Page

Access the EditoruseEditoruseEditorSelectoruseEditorStateOutside PlateEditor API and CommandsPlugin MethodsPlugin StoreNext Steps
Build your editor
Production-ready AI template and reusable components.
Get all-access
useEditorState(selector, options?)
Render while a controller may have no editor.useActiveEditor() and handle null.
components/bold-button.tsx
import { useEditor, useEditorSelector } from 'platejs/react';
 
import { Button } from '@/components/ui/button';
 
export function BoldButton() {
  const editor = useEditor();
  const hasSelection = useEditorSelector((editor) =>
    Boolean(editor.read.selection())
  );
 
  return (
    <Button
      disabled={!hasSelection}
      onClick={() => editor.update((tx) => tx.marks.toggle('bold'))}
    >
      Bold
    </Button>
  );
}
components/bold-button.tsx
import { useEditor, useEditorSelector } from 'platejs/react';
 
import { Button } from '@/components/ui/button';
 
export function BoldButton() {
  const editor = useEditor();
  const hasSelection = useEditorSelector((editor) =>
    Boolean(editor.read.selection())
  );
 
  return (
    <Button
      disabled={!hasSelection}
      onClick={() => editor.update((tx) => tx.marks.toggle('bold'))}




useEditor

useEditor returns the required stable editor object. Use it for event handlers, effects, commands, and reads that should not cause a render. It throws when no matching editor is active.

components/insert-paragraph-button.tsx
const editor = useEditor();
 
editor.update((tx) => {
  tx.nodes.insert({
    children: [{ text: 'Inserted paragraph' }],
    type: 'paragraph',
  });
});
components/insert-paragraph-button.tsx
const editor = useEditor();
 
editor.update((tx) => {
  tx.nodes.insert({
    children: [{ text: 'Inserted paragraph' }],
    type: 'paragraph',
  });
});

useEditorSelector

useEditorSelector subscribes to a derived value. Return a primitive or provide equalityFn when the selected value needs custom comparison.

components/selection-state.tsx
const isSelectionExpanded = useEditorSelector(
  (editor) => editor.read.selection.isExpanded()
);
components/selection-state.tsx
const isSelectionExpanded = useEditorSelector(
  (editor) => editor.read.selection.isExpanded()
);

useEditorState

useEditorState reads through the immutable state view and re-renders only when the selected result changes.

components/selection-debug.tsx
const selection = useEditorState((state) => state.selection());
 
return <pre>{JSON.stringify(selection, null, 2)}</pre>;
components/selection-debug.tsx
const selection = useEditorState((state) => state.selection());
 
return <pre>{JSON.stringify(selection, null, 2)}</pre>;

Outside Plate

Wrap shared UI in PlateController when a toolbar, side panel, or inspector lives outside a single <Plate> tree.

components/editor-shell.tsx
import type React from 'react';
 
import { PlateController, useActiveEditor } from 'platejs/react';
 
import { Button } from '@/components/ui/button';
 
export function EditorShell({ children }: { children: React.ReactNode }) {
  return (
    <PlateController>
      <ActiveEditorToolbar />
      {children}
    </PlateController>
  );
}
 
function ActiveEditorToolbar() {
  const editor = useActiveEditor();
 
  if (!editor) return null;
 
  return (
    <Button onClick={() => editor.api.dom.focus()}>Focus editor</Button>
  );
}
components/editor-shell.tsx
import type React from 'react';
 
import { PlateController, useActiveEditor } from 'platejs/react';
 
import { Button } from '@/components/ui/button';
 
export function EditorShell({ children }: { children: React.ReactNode }) {
  return (
    <PlateController>
      <ActiveEditorToolbar />
      {children}
    </PlateController>
  );
}
 
function ActiveEditorToolbar() {
  const editor = useActiveEditor();






PlateController resolves an editor by explicit id, focused editor, then primary editors. useActiveEditor() returns null while none is active. Components that require an editor should use useEditor() and fail fast.

Editor API and Commands

Use editor.read for snapshot-bound queries, editor.api for DOM, host, and plugin services, and editor.update for operations that change the document, selection, history, or plugin state.

editor-methods.ts
import { BoldPlugin } from '@platejs/basic-nodes/react';
 
const selection = editor.read.selection();
const selectedText = selection ? editor.read.text.string(selection) : '';
 
const currentBlock = editor.read.nodes.block();
const bold = editor.plugin(BoldPlugin);
 
if (selectedText && !bold.read.isActive()) {
  bold.update.toggle();
}
 
editor.update((tx) => {
  tx.nodes.insert({
    children: [{ text: 'New paragraph' }],
    type: 'paragraph',
  });
});
editor-methods.ts
import { BoldPlugin } from '@platejs/basic-nodes/react';
 
const selection = editor.read.selection();
const selectedText = selection ? editor.read.text.string(selection) : '';
 
const currentBlock = editor.read.nodes.block();
const bold = editor.plugin(BoldPlugin);
 
if (selectedText && !bold.read.isActive()) {
  bold.update.toggle();
}
 
editor.update((tx) => {
  tx.nodes.insert({
    children: [{ text: 


SurfaceExamplesUse for
editor.readtext.string, nodes.block, marks, selection.isExpandedReading the current immutable editor state.
editor.apidom.focus and plugin-owned service groupsDOM/runtime services and non-mutating feature APIs.
editor.updatetx.nodes.insert, tx.nodes.set, tx.marks.toggle, tx.selection.setMutating document or editor state.

Plugin Methods

Pass a plugin descriptor to editor.plugin(plugin) to open its typed portal. The portal exposes resolved descriptor fields and owns that plugin's scoped API, one-shot update commands, and live state.

html-methods.ts
import { BoldPlugin } from '@platejs/basic-nodes/react';
 
editor.plugin(BoldPlugin).update.toggle();
html-methods.ts
import { BoldPlugin } from '@platejs/basic-nodes/react';
 
editor.plugin(BoldPlugin).update.toggle();
MethodUse for
editor.plugin(plugin)Typed descriptor fields plus scoped API, reads, updates, and store.
editor.plugin(plugin).nameThe installed capability identity.
editor.plugin(ElementPlugin).schema.typeThe resolved persisted element type.
editor.plugin(BoldPlugin).schema.keyThe resolved persisted property key.
editor.plugin(plugin).inject.nodePropsCompiled injected node props for a plugin.

Runtime-name portals are intentionally erased. Use them for dynamic names or family-agnostic slots that accept whichever installed descriptor owns the name. Check .installed before any other field when the named plugin is optional; other fields throw when the plugin is absent.

Plugin Store

Use the scoped store when imperative code needs to read or write editor-local plugin state. Use usePluginStore when React UI needs to re-render from that state.

components/find-replace-control.tsx
import { FindReplacePlugin } from '@platejs/find-replace';
import { useEditor, usePluginStore } from 'platejs/react';
 
export function FindReplaceControl() {
  const editor = useEditor();
  const search = usePluginStore(FindReplacePlugin, 'search');
 
  return (
    <input
      value={search}
      onChange={(event) => {
        editor
          .plugin(FindReplacePlugin)
          .store.set({ search: event.target.value });
        editor.api.react.refreshDecorations();
      }}
    />
  );
}
components/find-replace-control.tsx
import { FindReplacePlugin } from '@platejs/find-replace';
import { useEditor, usePluginStore } from 'platejs/react';
 
export function FindReplaceControl() {
  const editor = useEditor();
  const search = usePluginStore(FindReplacePlugin, 'search');
 
  return (
    <input
      value={search}
      onChange={(event) => {
        editor
          .plugin(FindReplacePlugin)
          .store.set({ search: event.target.value });
        editor.api.react.refreshDecorations();
      }}


plugin-store.ts
const state = editor.plugin(FindReplacePlugin).store.get();
 
editor.plugin(FindReplacePlugin).store.set({
  search: 'Plate',
});
 
editor.plugin(FindReplacePlugin).store.set((draft) => {
  draft.search = draft.search.trim();
});
plugin-store.ts
const state = editor.plugin(FindReplacePlugin).store.get();
 
editor.plugin(FindReplacePlugin).store.set({
  search: 'Plate',
});
 
editor.plugin(FindReplacePlugin).store.set((draft) => {
  draft.search = draft.search.trim();
});
MethodUse for
editor.plugin(plugin).store.get(key, ...args)One state field or named selector result.
editor.plugin(plugin).store.get()Complete current plugin state.
editor.plugin(plugin).store.set(partial)Merge state fields.
editor.plugin(plugin).store.set(updater)Mutate state through a draft updater.

Next Steps

TaskGuide
Configure editor creation.Editor Configuration
Add plugin APIs and transaction commands.Plugin Methods
Read plugin context inside components.Plugin Context
Browse editor query contracts.Editor API
Browse editor transaction contracts.Editor Transactions
>
Bold
</Button>
);
}
if (!editor) return null;
return (
<Button onClick={() => editor.api.dom.focus()}>Focus editor</Button>
);
}
'New paragraph'
}],
type: 'paragraph',
});
});
/>
);
}