From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
  • Plate
  • Plitev42
    • Editor API
    • Editor Transforms
    • Node
    • Element
    • Text
    • Path
    • Point
    • Range
    • Location
    • Location Ref
    • Document Change
  • Plate Core
    • Plate Components
    • Plate Editor
    • Plate Plugin
    • Plate Store
    • Plate Controller
  • Plate Utils
  • React Utils
  • cn
  • Floating
  • Resizable

Plate Plugin

PreviousNext

API reference for Plate plugins.

Plate plugins are descriptors passed to the Plate plugins prop. Installed plugin APIs are inferred on editor.api under the plugin name. Pass the descriptor to editor.plugin(MyPlugin) when generic code needs the exact plugin API, update commands, identity, or editor-local state; React components subscribe with usePluginStore.

Authoring Context

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

export type UploadPluginState = {
  active: boolean;
};
 
const uploadInitialState





























Plate EditorPlate Store

On This Page

Authoring ContextPlugin PropertiesPlugin MethodsPlugin ContextGeneric Types
Build your editor
Production-ready AI template and reusable components.
Get all-access
:
UploadPluginState
=
{
active: false,
};
export const UploadPlugin = definePlatePlugin("upload", {
schema: {
element: {
content: schema.content.text({ default: 'text', min: 1 }),
},
},
initialState: uploadInitialState,
api: ({ store }) => ({
start: () => store.set({ active: true }),
}),
read: ({ state }) => ({
isReady: () => !!state.selection(),
}),
update: ({ tx, type }) => ({
insert: () => tx.nodes.insert({ children: [{ text: "" }], type }),
}),
}).extend(({ api, read, store, update }) => ({
on: {
focus: () => {
if (!read.isReady()) return;
store.set({ active: true });
api.start();
update.insert();
},
},
}));
export type UploadPluginState = {
  active: boolean;
};
 
const uploadInitialState: UploadPluginState = {
  active: false,
};
 
export const UploadPlugin = definePlatePlugin("upload", {
  schema: {
    element: {
      content: schema.content.text({ default: 'text', min: 1 }),
    },
  },
  initialState: uploadInitialState,
  api: ({ store }) => ({
    start: () => store.set({ active: true }),
  }),
  read: ({ state }) => ({
    isReady: () => !!state.selection(),
  }),
  update: ({ tx, type }) => ({
    insert: () => tx.nodes.insert({ children: [{ text: "" }], type }),
  }),
}).extend(({ api, read, store, update }) => ({
  on: {
    focus: () => {
      if (!read.isReady()) return;
 
      store.set({ active: true });
      api.start();
      update.insert();
    },
  },
}));

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; Plite 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()). 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 for this plugin's node. Declare it in defineBasePlugin(name, { component }) or definePlatePlugin(name, { component }) for static/RSC and live rendering. Replace it with one terminal .configure({component}). Base .extend() does not accept it. Use toPlatePlugin() 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 Plite 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.

    Provides component replacement and weak cross-package plugin adaptation.

    Defines whole-input HTML parsing behavior.

    Defines how the plugin renders components.

    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.

    Plate plugin or raw Plite extension descriptors that must be installed before this plugin. Pass the descriptor objects themselves so Plate preserves dependency identity and type inference.

    Plate plugin or raw Plite extension descriptors that cannot be installed with this plugin. Import both reference types from platejs; Plite-only libraries can import EditorExtensionReference from @platejs/plite.

    Typed middleware over declared Plite editorReads descriptors.

    Pure typed command interceptors declared with handle or around.

    Deterministic changed-range structural repairs.

    Typed persisted or runtime state descriptors.

    Typed commit-effect descriptors and codecs.

    Derived state providers.

    Ordered values bound to typed extension points. Host clipboard insertion is a direct clipboardHandler(...) contribution, never a root clipboard field. clipboardHandler({ insertData }) is the sole form. The owning extension or Plate stage contextually infers transaction from its installed update capabilities. next delegates to the next handler.

    Owns synchronous resources. Defer work that must observe publication with context.afterPublish(...).

    Validates the detached candidate before activation.

    Enables or disables the plugin. Used by Plate to determine if the plugin should be used.

    Property used by Plate to decorate editor ranges.

    Prepares complete document input after application migrations and before schema fitting. It runs during initialization and complete editor.update.value.replace(...) loads. Use it for installed current-schema invariants, not source-version migrations.

    Hook called when the editor is initialized.

    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
      prepareDocument?: boolean; // default: false
    };
    type EditOnlyConfig = {
      render?: boolean; // default: true
    
    
    
    

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 extensions 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 defineBasePlugin() or definePlatePlugin(). Complete every .extend() call before applying consumer .configure(). The returned PlatePlugin 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, facetProviders, contributions, on, activate, and : native Plite capabilities declared directly on the plugin

Plugin Context

Attributes

    The current editor instance.

    The current plugin instance.

    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 = definePlatePlugin("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 = definePlatePlugin("myPlugin", {
  initialState: {
    customOption: false,
  } satisfies MyPluginState,
  api: ({ store }) => ({
    getData: () => String(store.get("customOption")),
  }),
  update: ({ tx }) => ({
    run: () => tx.selection.collapse(),
  }),
});

on
?:
boolean
;
// default: true
inject?: boolean; // default: true
prepareDocument?: boolean; // default: false
};

When set to true (boolean):

  • render, on, and inject.nodeProps are only active when editor is not read-only
  • prepareDocument remains active regardless of read-only state

When set to an object:

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

Examples:

// All features (except prepareDocument) are edit-only
editOnly: true;
 
// prepareDocument is edit-only, others remain edit-only by default
editOnly: {
  prepareDocument: true;
}
 
// render is always active, others follow default behavior
editOnly: {
  render: false;
}
// All features (except prepareDocument) are edit-only
editOnly: true;
 
// prepareDocument is edit-only, others remain edit-only by default
editOnly: {
  prepareDocument: true;
}
 
// render is always active, others follow default behavior
editOnly: {
  render: false;
}
validate
  • codecs: the declaration returned by context-bound defineCodecs
  • Independently reusable standalone Plite descriptors use defineExtension from @platejs/plite. 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.

    defineBasePlugin() / definePlatePlugin() own every independent declaration field, including api, read, selectors, update, native Plite fields, and codecs.

    type MyDefinition = DefinitionOf<typeof MyPlugin>;