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 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 useEditorRef() and useEditorMounted() normally read the nearest Plate store. Inside PlateController, the same hooks can resolve a store outside a specific editor tree.

LookupBehavior
useEditorRef('main')Resolves the store registered for main.
useEditorRef()Resolves the active editor store, then the first mounted primary editor store.
Missing store with controllerReturns the fallback store, so useEditorRef() returns a fallback editor.
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. fallback store when no store is available

Fallback Editors

The fallback editor exists so read-only UI can render while no editor is active. It is not safe for transforms.

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

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 Slate 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.