From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
  • Plate
  • Plitev42
    • Editor API
    • Editor Transforms
    • Node
    • Element
    • Text
    • Path
    • Point
    • Range
    • Location
    • Location Ref
    • Document Change
  • Plate Core
    • Plate Components
    • Plate Editor
    • Plate Plugin
    • Plate Store
    • Plate Controller
  • Plate Utils
  • React Utils
  • cn
  • Floating
  • Resizable

Plate Controller

PreviousNext

API reference for PlateController.

PlateController lets UI outside a single <Plate> subtree read the active editor store. Use it for shared toolbars, side panels, inspectors, and multi-editor shells.

Quick Use

Wrap the shared UI and all editors in PlateController. PlateContent registers each mounted editor store through PlateControllerEffect.

components/editor-shell.tsx
import {
  Plate,
  PlateContent,
  PlateController,
  usePlateEditor,
} from 'platejs/react';
 
export function





























Plate StorePlate Utils

On This Page

Quick UseActive Editor LookupOptional Active EditorsRegistrationAPI ReferencePlateControllerController Store StateusePlateControllerStoreusePlateControllerExistsusePlateControllerLocalStorePlateControllerEffect
Build your editor
Production-ready AI template and reusable components.
Get all-access
EditorShell
() {
return (
<PlateController>
<ActiveEditorLabel />
<MainEditor />
<SecondaryEditor />
</PlateController>
);
}
function MainEditor() {
const editor = usePlateEditor({
id: 'main' });
return (
<Plate editor={editor}>
<PlateContent />
</Plate>
);
}
function SecondaryEditor() {
const editor = usePlateEditor({
id: 'secondary' });
return (
<Plate editor={editor} primary={false}>
<PlateContent />
</Plate>
);
}
components/editor-shell.tsx
import {
  Plate,
  PlateContent,
  PlateController,
  usePlateEditor,
} from 'platejs/react';
 
export function EditorShell() {
  return (
    <PlateController>
      <ActiveEditorLabel />
      <MainEditor />
      <SecondaryEditor />
    </PlateController>
  );
}
 
function MainEditor() {
  const editor = usePlateEditor({
     id: 'main' });
 
  return (
    <Plate editor={editor}>
      <PlateContent />
    </Plate>
  );
}
 
function SecondaryEditor() {
  const editor = usePlateEditor({
     id: 'secondary' });
 
  return (
    <Plate editor={editor} primary={false}>
      <PlateContent />
    </Plate>
  );
}

primary belongs on Plate, not on createPlateEditor or usePlateEditor.

Active Editor Lookup

Hooks such as useEditor() and useEditorMounted() normally read through the nearest Plate store. Inside PlateController, the same hooks can resolve an editor outside a specific editor tree.

LookupBehavior
useEditor({ id: 'main' })Resolves the active editor registered for main; throws when it is absent.
useEditor()Resolves the active editor, then the first mounted primary editor; throws when none exists.
useActiveEditor()Uses the same lookup and returns null when no editor is active.
Missing store without controllerThrows Plate hooks must be used inside a Plate or PlateController.

Controller lookup order without an explicit ID:

  1. activeId
  2. each ID in primaryEditorIds
  3. no active editor

Optional Active Editors

Plate keeps an inert editor internally so hook subscriptions retain a stable call shape. Public code never receives it. Use useActiveEditor() for UI that can render while no editor is active; use useEditor() when absence is a programming error.

components/active-editor-label.tsx
import { useActiveEditor } from 'platejs/react';
 
export function ActiveEditorLabel() {
  const editor = useActiveEditor();
 
  if (!editor) return <p>No editor selected.</p>;
 
  return <p>Active editor: {editor.id}</p>;
}
components/active-editor-label.tsx
import { useActiveEditor } from 'platejs/react';
 
export function ActiveEditorLabel() {
  const editor = useActiveEditor();
 
  if (!editor) return <p>No editor selected.</p>;
 
  return <p>Active editor: {editor.id}</p>;
}

useEditor() is the shorter path for commands and components that require an active editor because it fails at the missing provider instead of letting a mutation disappear into fallback state.

Registration

PlateControllerEffect runs inside PlateContent. It registers the current Plate store by editor ID, appends primary editors to primaryEditorIds, removes them on unmount, and sets activeId when Plite focus enters that editor.

StateOwnerBehavior
editorStoresPlateControllerEffectMaps mounted editor IDs to their Jotai stores. Unmounted IDs are set to null.
primaryEditorIdsPlateControllerEffectAppends mounted editors whose Plate store has primary: true; removes them on unmount.
activeIdPlateControllerEffectSet to the focused editor ID. Cleared on unmount when the unmounted editor was active.

API Reference

PlateController

Provider for cross-editor lookup state.

Props

    Shared UI and editor trees that should participate in controller lookup.

    Initial active editor ID.

    Initial editor-store map.

    Initial primary editor ID list.

Controller Store State

StateTypeDefault
activeIdstring | nullnull
editorStoresRecord<string, JotaiStore | null>{}
primaryEditorIdsstring[][]

usePlateControllerStore

Resolve a Plate Jotai store from the controller.

Parameters

    Editor ID to resolve directly.

ReturnsJotaiStore | null

    Matching editor store, active editor store, first mounted primary editor store, or null.

usePlateControllerExists

Check whether a local controller provider exists.

Returnsboolean

    true when usePlateControllerLocalStore() finds a controller store.

usePlateControllerLocalStore

Read the local controller atom store.

Parameters

    Scope options passed to the generated controller store hook. A string is treated as scope.

ReturnsPlateControllerStore

    Local controller store hook result.

PlateControllerEffect

Register a Plate store with the nearest controller.

Props

    Editor ID to register. Defaults to the ID from the current Plate store.

PlateContent renders PlateControllerEffect for you. Render it directly only when you build a custom content surface that still needs controller registration.