Plate
PlateEditorsTemplates
GitHub16kGitHub
DiscordDiscord
  • Plate
  • Editor API
  • Editor Transforms
  • Node
  • Element
  • Text
  • Path
  • Point
  • Range
  • Location
  • Anchor
  • Selection
  • Document Change
  • DOM API
  • React Hooks
  • Plate Core
    • Plate Components
    • Plate Editor
    • Plate Plugin
    • Editor Context
    • Plate Controller
  • Plate Utils
  • Resizable

Plate Plugin

PreviousNext

API reference for Plate plugins.

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.

Authoring context

Plugin constructor, plugin, configuration, event, codec, and injection callbacks receive the current plugin context when their callback contract supports it:

import { definePlugin, schema } from

























Plate EditorEditor Context

On This Page

Authoring contextPlugin propertiesPlugin methodsPlugin contextGeneric types
Build your editor
Production-ready AI template and reusable components.
Get all-access
'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 }),
}),
});
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.

Plugin properties

Attributes

    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.

    • Default: []

    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
    

Plugin methods

Methods

    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 services
    • read: snapshot or transaction-local reads
    • selectors: pure projections of editor-local plugin state
    • update: plugin-scoped transaction-bound mutations
    • readMiddleware, commands, corrections, stateFields, effectTypes, contributions, on, , and : editor capabilities declared directly on the plugin

Plugin context

Attributes

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

Generic types

Use DefinitionOf<typeof Plugin> as the sole public way to extract a descriptor's inferred definition.

Attributes

    The inferred definition contract, including name, initialState, api, read, update, selectors, dependencies, and schema inference.

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(),
  }),
});

?:
boolean
;
// default: true
};

When set to true (boolean):

  • render, on, and inject.nodeProps are only active when editor is not read-only

When set to an object:

  • Each property can be individually configured
  • Properties default to being edit-only (true)
  • Set a property to false to make it always active regardless of read-only state

Examples:

// 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;
}
activate
validate
  • codecs: the declaration returned by context-bound defineCodecs
  • Independently 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.

    type MyDefinition = DefinitionOf<typeof MyPlugin>;