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.
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>
);
}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.
| Lookup | Behavior |
|---|---|
useEditorRef('main') | Resolves the store registered for main. |
useEditorRef() | Resolves the active editor store, then the first mounted primary editor store. |
| Missing store with controller | Returns the fallback store, so useEditorRef() returns a fallback editor. |
| Missing store without controller | Throws Plate hooks must be used inside a Plate or PlateController. |
Controller lookup order without an explicit ID:
activeId- each ID in
primaryEditorIds - 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.
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>;
}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>;
}Check useEditorMounted(id?) or !editor.meta.isFallback before running
transforms from UI that lives under PlateController.
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.
| State | Owner | Behavior |
|---|---|---|
editorStores | PlateControllerEffect | Maps mounted editor IDs to their Jotai stores. Unmounted IDs are set to null. |
primaryEditorIds | PlateControllerEffect | Appends mounted editors whose Plate store has primary: true; removes them on unmount. |
activeId | PlateControllerEffect | Set to the focused editor ID. Cleared on unmount when the unmounted editor was active. |
API Reference
PlateController
Provider for cross-editor lookup state.
Controller Store State
| State | Type | Default |
|---|---|---|
activeId | string | null | null |
editorStores | Record<string, JotaiStore | null> | {} |
primaryEditorIds | string[] | [] |
usePlateControllerStore
Resolve a Plate Jotai store from the controller.
usePlateControllerExists
Check whether a local controller provider exists.
usePlateControllerLocalStore
Read the local controller atom store.
PlateControllerEffect
Register a Plate store with the nearest controller.
PlateContent renders PlateControllerEffect for you. Render it directly only when you build a custom content surface that still needs controller registration.