Plugin context is the object Plate passes to plugin configuration callbacks,
events, native capabilities, transaction commands, and render components. It gives you
the resolved editor, current plugin name and schema handles, api, and plugin store
without reaching through global state. Use it inside plugin-owned code; use
editor methods or React hooks when code runs outside a plugin callback.
PlatePluginContext extends the shared plugin context with a React
PlateEditor. The same helper names are available in headless Plate base plugins,
but the editor type is BaseEditor.
| Property | Use for |
|---|---|
editor | The resolved editor instance. |
plugin| The resolved plugin configuration for the current plugin. |
name | Capability identity and API/update namespace. Never use it as an element type or property key. |
schema.type | Persisted identity for an element-owning plugin. |
schema.key | Persisted identity for a primary-property plugin. |
schema.properties | Compiled handles for additional properties declared by the current plugin. |
api | API owned by the current plugin. |
read | State-bound reads owned by the current plugin. |
update | One-shot updates owned by the current plugin. |
store | Read, update, or subscribe to the current plugin's editor-local state. |
defineCodecs(map) | Bind a self/product codec map to this plugin's inferred schema. |
defineCodecs(TargetPlugin, map) | Bind a foreign codec map to an exact descriptor and inject its target. |
Event callbacks receive context plus the event or lifecycle payload. Use the context helpers instead of closing over editor state.
import { definePlatePlugin } from 'platejs/react';
export type CounterPluginState = {
count: number;
enabled: boolean;
};
export const CounterPlugin = definePlatePlugin('counter', {
initialState: {
count: 0,
enabled: true,
} satisfies CounterPluginState,
on: {
keyDown: ({ event, name, store }) => {
if (!store.get('enabled')) return;
if (event.key === '+') {
store.set((state) => {
state.count += 1;
});
console.info(`${name} count incremented`);
}
},
},
});import { definePlatePlugin } from 'platejs/react';
export type CounterPluginState = {
count: number;
enabled: boolean;
};
export const CounterPlugin = definePlatePlugin('counter', {
initialState: {
count: 0,
enabled: true,
} satisfies CounterPluginState,
on: {
keyDown: ({ event, name, store }) => {
if (!store.get('enabled'))
store is scoped to CounterPlugin in this example.
Configuration, native capability, selector, API, transaction, and editor override callbacks also receive plugin context.
import { definePlatePlugin } from 'platejs/react';
export type CounterPluginState = {
count: number;
};
export const CounterPlugin = definePlatePlugin('counter', {
initialState: {
count: 0,
} satisfies CounterPluginState,
api: ({ store }) => ({
isEmpty: () => store.get('count') === 0,
}),
selectors: {
label: (state) => `Count: ${state.count}`,
},
});import { definePlatePlugin } from 'platejs/react';
export type CounterPluginState = {
count: number;
};
export const CounterPlugin = definePlatePlugin('counter', {
initialState: {
count: 0,
} satisfies CounterPluginState,
api: ({ store }) => ({
isEmpty: () => store.get('count') === 0,
}),
selectors: {
label: (state) => `Count: ${state
Selectors are readable through store.get and subscribable through
usePluginStore. They are pure state-first functions and cannot read the
editor or another plugin.
Inside update, pass a property patch object. Plate infers each key and value
from the current plugin and its required dependencies:
const LineHeightPlugin = defineBasePlugin('lineHeight', {
schema: {
properties: {
lineHeight: schema.elementProperty(property.number()),
},
},
update: ({ tx }) => ({
set: (value: number) => tx.nodes.set({ lineHeight: value }),
unset: () => tx.nodes.unset('lineHeight'),
}),
});const LineHeightPlugin = defineBasePlugin('lineHeight', {
schema: {
properties: {
lineHeight: schema.elementProperty(property.number()),
},
},
update: ({ tx }) => ({
set: (value: number) => tx.nodes.set({ lineHeight: value }),
unset: () => tx.nodes.unset('lineHeight'),
}),
});Use the property handle's exact key as a computed object key for an aliased property. Prefix families and cross-node behavior belong in a semantic plugin update method.
Declare native Plite fields directly in the constructor or a staged
.extend() callback. Their capability-specific helpers and Plate plugin
context are inferred together. Extract domain inputs instead of the whole
Plate plugin context.
Use defineExtension from @platejs/plite only for independently
reusable standalone Plite descriptors. See
Plugin Methods.
Use defineCodecs in the constructor's codecs callback. It is the codec
map's single inference anchor:
import { defineBasePlugin, property } from 'platejs';
export const StrongPlugin = defineBasePlugin('strong', {
codecs: ({ defineCodecs }) =>
defineCodecs({
'text/html': {
decode: () => true,
decodeOnly: true,
match: [{ tag: 'strong' }],
},
}),
schema: { mark: property.boolean() },
});import { defineBasePlugin, property } from 'platejs';
export const StrongPlugin = defineBasePlugin('strong', {
codecs: ({ defineCodecs }) =>
defineCodecs({
'text/html': {
decode: () => true,
decodeOnly: true,
match: [{ tag: 'strong' }],
},
}),
schema: { mark: property.boolean() },
});The one-argument form owns self and product maps. For a foreign contribution,
call defineCodecs(TargetPlugin, map); Plate injects the target into every HTML
rule. Keep the map MIME-keyed, and use either one 'text/html' rule or a
non-empty ordered rule tuple. Do not author direct codec maps or annotate the
callbacks.
Use editor.plugin(Plugin) when plugin-owned code needs another plugin's
consumer portal.
import { LinkPlugin } from '@platejs/link/react';
import { definePlatePlugin } from 'platejs/react';
export const LinkAwarePlugin = definePlatePlugin('linkAware', {
dependencies: [LinkPlugin],
on: {
keyDown: ({ editor, event }) => {
if (event.key !== 'Enter') return;
const link = editor.plugin(LinkPlugin);
console.info(`Link element type: ${link.schema.type}`);
},
},
});import { LinkPlugin } from '@platejs/link/react';
import { definePlatePlugin } from 'platejs/react';
export const LinkAwarePlugin = definePlatePlugin('linkAware', {
dependencies: [LinkPlugin],
on: {
keyDown: ({ editor, event }) => {
if (event.key !== 'Enter') return;
const link = editor.plugin(LinkPlugin);
console.info(`Link element type: ${link.schema.type}`);
},
},
});Declare a dependency when the plugin cannot work without that capability. Plate installs dependencies before their dependents, so the portal is available in the handler. Keep cross-plugin writes rare; they couple two plugins tightly.
Use useEditorPlugin inside a component rendered under <Plate>. It returns
the plugin's flat consumer portal. Call useEditor() separately when the
component also needs the editor.
import { useEditorPlugin, usePluginStore } from 'platejs/react';
import { CounterPlugin } from './counter-plugin';
export function CounterBadge() {
const { name } = useEditorPlugin(CounterPlugin);
const count = usePluginStore(CounterPlugin, 'count');
const label = usePluginStore(CounterPlugin, 'label');
return (
<span data-plugin-name={name}>
{label} ({count})
</span>
);
}import { useEditorPlugin, usePluginStore } from 'platejs/react';
import { CounterPlugin } from './counter-plugin';
export function CounterBadge() {
const { name } = useEditorPlugin(CounterPlugin);
const count = usePluginStore(CounterPlugin, 'count');
const label = usePluginStore(CounterPlugin, 'label');
return (
<span data-plugin-name={name}>
{label} ({count})
</span>
);
}Use a selector callback when a component needs a derived value from several state fields.
import { usePluginStore } from 'platejs/react';
import { CounterPlugin } from './counter-plugin';
export function CounterStatus() {
const status = usePluginStore(CounterPlugin, (state) =>
state.count === 0 ? 'empty' : 'active'
);
return <span>{status}</span>;
}import { usePluginStore } from 'platejs/react';
import { CounterPlugin } from './counter-plugin';
export function CounterStatus() {
const status = usePluginStore(CounterPlugin, (state) =>
state.count === 0 ? 'empty' : 'active'
);
return <span>{status}</span>;
}For code outside the nearest <Plate> provider, pass an editor explicitly with
useEditorPluginStore.
Plugin state is stored per editor. Updating one editor's plugin store does not update another editor.
export const CounterPluginWithInitialCount = CounterPlugin.extend(
({ store }) => ({
initialState: {
count: store.get().count + 1,
},
})
);export const CounterPluginWithInitialCount = CounterPlugin.extend(
({ store }) => ({
initialState: {
count: store.get().count + 1,
},
})
);store.set accepts either a partial object or a draft callback.
import type { PlateEditor } from 'platejs/react';
import { CounterPlugin } from './counter-plugin';
export function resetCounter(editor: PlateEditor) {
const { store } = editor.plugin(CounterPlugin);
store.set({
count: 1,
});
store.set((draft) => {
draft.count += 1;
});
}import type { PlateEditor } from 'platejs/react';
import { CounterPlugin } from './counter-plugin';
export function resetCounter(editor: PlateEditor) {
const { store } = editor.plugin(CounterPlugin);
store.set({
count: 1,
});
store.set((draft) => {
draft.count += 1;
});
}Plate throws when store.get or usePluginStore targets a missing state field
or selector.
Always pass the plugin descriptor to usePluginStore and
useEditorPluginStore. A name-only object has no state contract for TypeScript
to infer.
| Helper | Scope | Notes |
|---|---|---|
editor.plugin(plugin) | Any editor code. | Opens the installed plugin's typed consumer portal. |
useEditorPlugin(plugin, id?) | React under <Plate>. | Returns the installed plugin's flat consumer portal. |
usePluginStore(plugin, key, ...args) | React under <Plate>. | Subscribes to one state field or named selector. |
usePluginStore(plugin, selector, options?) | React under <Plate>. | Subscribes to a value derived from plugin state. |
useEditorPluginStore(editor, plugin, key, ...args) | React with explicit editor. | Use outside the closest editor provider. |
useEditorPluginStore(editor, plugin, selector, options?) | React with explicit editor. | Explicit-editor variant of usePluginStore. |
For plugin extension methods, see Plugin Methods. For plugin configuration, see Plugin.