Plate plugins are descriptors passed in the plugins array of createEditor
or useCreateEditor. Installed plugin APIs are inferred on editor.api under
the plugin name. Pass the descriptor to editor.plugin(MyPlugin) for its exact
API, reads, update commands, schema identity, and editor-local state. React
components use useEditor().plugin(MyPlugin) for the portal and usePluginStore
for state subscriptions.
Plugin constructor, plugin, configuration, event, codec, and injection callbacks receive the current plugin context when their callback contract supports it:
import { definePlugin, schema } from
import { definePlugin, schema } from 'platejs/react';
export type UploadPluginState = {
active: boolean;
};
const uploadInitialState: UploadPluginState = {
active: false,
};
export const UploadPlugin = definePlugin("upload", {
schema: {
element: {
content: schema.content.text({ default: 'text', min: 1 }),
},
},
initialState: uploadInitialState,
api: ({ store }) => ({
isActive: () => store.get('active'),
}),
read: ({ state }) => ({
isReady: () => !!state.selection(),
}),
update: ({ tx, schema }) => ({
insert: () => tx.nodes.insert({ children: [{ text: "" }], type: schema.type }),
}),
});Use store, api, read, update, name, plugin, and installed
directly for the current plugin. Use editor for editor-wide operations,
another plugin, or transaction metadata unavailable on the scoped update
facade. Specialized callbacks such as shortcut handlers and input rules may
only receive editor; use an exact typed plugin portal there.
Unique identifier Plate uses to resolve the descriptor through editor.plugin(MyPlugin).
Plugin descriptors or dynamic names targeted by the plugin's schema contributions and injected behavior.
[]Plugin-owned API functions exposed through editor.api[MyPlugin.name] and the
exact editor.plugin(MyPlugin).api portal. Both paths reference the same
immutable API object. Declare api as a factory even when it needs no
context. The factory receives one object containing the normal Plate plugin
context; it never receives positional editor, context arguments. Authors
declare independent methods in the constructor and use .extend({api}) only
when they need an earlier-stage type or are adapting an imported/prebuilt
descriptor. Genuinely editor-wide capabilities use the same root api field;
Plate projects it under the plugin name.
State-bound reads owned by the plugin. Plate publishes them under
editor.read[name] and through editor.plugin(MyPlugin).read. Declare
read as a factory, even when the returned methods need no authoring context.
Plate constructs the namespace once per plugin configuration. Return methods
or nested method records; compute document values when a method runs. Stable
constants and host services belong in api.
Pure state-first derivations over the plugin's editor-local store. React
consumers subscribe to them through usePluginStore.
Transaction commands provided by the plugin. Call one through
editor.plugin(MyPlugin).update.method() or compose it inside
editor.update((tx) => tx.plugin(MyPlugin).method()). Pass MyPlugin.name
instead when the caller should not depend on the descriptor's package.
update is factory-only: return the command object from the callback instead
of declaring a static object.
The seed for this plugin's mutable editor-local store. Package authors declare
an exported *PluginState beside an exported plugin and check its defaults
through a typed constant or explicit factory return type. Use the callback form
when the seed depends on the resolved plugin context. App consumers override
the seed with one final .configure({ initialState: { ... } }). Runtime code
reads and updates the installed store through editor.plugin(MyPlugin).store.
The ordinary component or intrinsic HTML tag for this plugin's node. Declare it in
definePlugin(name, { component }) or
definePlugin(name, { component }) for
static/RSC and live rendering. Replace it with one terminal
.configure({component}). Base .extend() does not accept it. Use
toReactPlugin() at the owning React adapter to publish a reusable
Plate-layer descriptor or add Plate-only authoring; a terminal consumer does
not convert merely to set component.
Schema-aware product and foreign format mappings. Use the contextual
defineCodecs(map) helper for self/product codecs or
defineCodecs(TargetPlugin, map) for foreign contributions.
Lifecycle and DOM event handlers. Child names do not repeat the on prefix:
use nodeChange, textChange, keyDown, paste, and their capture variants.
Defines how the plugin injects functionality into other plugins or the editor.
Declares the plugin's Plate model through its element, mark, keyed
properties, and contentRoots fields. A schema factory receives the plugin
name, its configured initialState, targetElementTypes, and Plate
relationship helpers. name identifies the capability only. Installed AST
identity is published through schema.type for an element plugin and
schema.key for a primary-mark plugin. Author callbacks may also use
schema.properties.<localId> for additional declared properties; consumer
portals do not expose that map. Behavior and aggregate-property portals omit
schema.
Boolean text properties use
property.boolean({ default: false, omitDefault: true }).
Inside update, tx.nodes.set(props, options) accepts a typed property patch,
while unset(key, options) removes exact properties. Aliases use the exact
authored property handle's key in a computed patch. Prefix families and
cross-node behavior use semantic update methods.
Defines editing behavior for the plugin's compiled schema identity.
Weakly adapts already-installed foreign plugins by name. Each key is a target
plugin name. Missing targets are ignored. Values cannot define name,
dependencies, schema, or another override. Contributors resolve by
priority then source order, and direct target .configure() values remain
authoritative. A peer may replace the target component; use
{ enabled: false } when a package plugin must disable an optional installed
target.
Defines whole-input HTML parsing behavior.
Defines rendering for the plugin's primary component and mounted views.
Defines structural composition around Plate surfaces and rendered nodes. Each slot accepts one component. Replacing a slot replaces that component; compose multiple wrappers with JSX inside the slot. A replacement wrapRoot owns any required feature integration and cleanup.
Defines keyboard shortcuts for the plugin.
Input rules owned by this plugin. Use the typed rule factories and keep the rules beside the feature that owns the resulting behavior.
Plugin or plugin descriptors that must be installed before this plugin. Pass the descriptor objects themselves so Plate preserves dependency identity and type inference.
Plugin or plugin descriptors that cannot be installed
with this plugin. Import both reference types from platejs.
Typed middleware over declared Plate editorReads descriptors.
Pure typed command interceptors declared with handle or around. DOM paste
policy intercepts domCommands.insertData here and returns a transaction spec
from state.transaction(...).
Deterministic changed-range structural repairs.
Typed persisted or runtime state descriptors.
Typed commit-effect descriptors and codecs.
Ordered values bound to typed plugin points. Use them for declarative values
collected by a specific plugin owner. Clipboard ingress composes through the
domCommands.insertData command.
Runs early and owns synchronous resources. Register
context.beforePublish(callback) for validation or resource initialization
that needs the final document candidate. Its signature is
beforePublish: (callback: () => void) => void. The callback runs before
publication becomes permanent, during both editor construction and dynamic
plugin changes. It must be synchronous and must not write to the document.
Throwing vetoes publication and rolls back the candidate.
Register resource cleanup with context.onCleanup(...), including resources
acquired in beforePublish. Cleanup belongs to the activation and runs on
rollback, removal, or replacement.
Use context.afterPublish(...) for nonthrowing observation of published
state. Callback errors are reported; they cannot veto or roll back publication.
Checks the assembled detached candidate before activation. This early check
does not observe the final initialized document. Register checks that need
that document through beforePublish inside activate.
Enables or disables the plugin. Used by Plate to determine if the plugin should be used.
Supplies transient keyed ranges through read({ entry, ...context }).
observe({ refresh, ...context }) owns source invalidation and cleanup;
static rendering reads the source without subscribing.
attributes accepts a DecorationAttributes object, a pure callback
receiving the inferred plugin context plus entry and decoration, or
null to clear inherited presentation. Configure an existing source's
attributes without repeating its reader or observer. A new source requires
read.
Presentation classes concatenate with semantic classes, styles merge
shallowly, and other supplied attributes override the reader's attributes.
Use className, style, data-*, and aria-*; inline markup and event
handlers belong to components. Callbacks run after each source read and
add no subscription. Changes to external inputs require the source's
observe contract.
Configures which plugin functionalities should only be active when the editor is not read-only.
Can be either a boolean or an object configuration:
type EditOnlyConfig = {
render?: boolean; // default: true
on?: boolean; // default: true
inject?: boolean; // default: true
};type EditOnlyConfig = {
render?: boolean; // default: true
on?: boolean; // default: true
inject
Applies one terminal consumer configuration and returns a descriptor that
cannot be configured or extended again. Use the object form for definition
fields. The callback form can derive existing initialState, on, render,
or shortcuts from the resolved editor context. Contextual plugins
declared before this call read the configured values, while the configuration
remains the final override.
HeadingPlugin.configure({
rules: { break: { empty: 'reset' } },
});HeadingPlugin.configure({
rules: { break: { empty: 'reset' } },
});Adds a contribution to an imported/prebuilt descriptor, a shared factory the
constructor cannot access, or types introduced by an earlier contribution. Put independent
fields directly in definePlugin(). Complete
every .extend() call before applying consumer .configure(). The returned
Plugin carries the contribution's inferred capabilities.
The contribution fields have distinct owners:
api: plugin-scoped immutable servicesread: snapshot or transaction-local readsselectors: pure projections of editor-local plugin stateupdate: plugin-scoped transaction-bound mutationsreadMiddleware, commands, corrections, stateFields, effectTypes,
contributions, on, , and
: editor capabilities declared directly on the pluginThe current editor instance.
The current plugin supplied by the callback. Let the constructor or
plugin infer it. Consumer code obtains its portal by passing the
descriptor to editor.plugin(MyPlugin) or useEditor().plugin(MyPlugin).
Creates a schema-checked codec declaration inside the constructor's
codecs callback, or inside .extend() when the codec needs an earlier
capability. Pass one MIME-keyed map for self/product codecs, or pass a
target plugin plus the map for a foreign contribution.
Reads, updates, and subscribes to the current plugin's editor-local state. Named selectors are pure functions of that state.
For more detailed information on specific aspects of Plate plugins, refer to the individual guides on Plugin Configuration, Plugin Methods, Plugin Context, Plugin Components, and Plugin Shortcuts.
Use DefinitionOf<typeof Plugin> as the sole public way to extract a
descriptor's inferred definition.
Usage example:
export type MyPluginState = {
customOption: boolean;
};
export const MyPlugin = definePlugin("myPlugin", {
initialState: {
customOption: false,
} satisfies MyPluginState,
api: ({ store }) => ({
getData: () => String(store.get("customOption")),
}),
update: ({ tx }) => ({
run: () => tx.selection.collapse(),
}),
});
type MyDefinition = DefinitionOf<typeof MyPlugin>;export type MyPluginState = {
customOption: boolean;
};
export const MyPlugin = definePlugin("myPlugin", {
initialState: {
customOption: false,
} satisfies MyPluginState,
api: ({ store }) => ({
getData: () => String(store.get("customOption")),
}),
update: ({ tx }) => ({
run: () => tx.selection.collapse(),
}),
});
When set to true (boolean):
render, on, and inject.nodeProps are only active when editor is not read-onlyWhen set to an object:
true)false to make it always active regardless of read-only stateExamples:
// All supported features are edit-only
editOnly: true;
// render is always active, others follow default behavior
editOnly: {
render: false;
}// All supported features are edit-only
editOnly: true;
// render is always active, others follow default behavior
editOnly: {
render: false;
}activatevalidatecodecs: the declaration returned by context-bound defineCodecsIndependently reusable standalone Plate descriptors use
definePlugin from platejs. Plate plugins declare the same
native capabilities at their root.
Use defineCodecs(map) for self/product codecs and
defineCodecs(TargetPlugin, map) for foreign codecs. The helper injects the
foreign target. The map remains MIME-keyed; its 'text/html' value accepts one
schema-aware rule or a non-empty ordered rule tuple. defineCodecs is the one
inline inference anchor. Do not author direct codec maps or manual target
fields.
definePlugin() / definePlugin() own every independent declaration
field, including api, read, selectors, update, editor fields, and
codecs.