Back to Plate

Plugin Methods

content/docs/(guides)/plugin-methods.mdx

53.0.812.8 KB
Original Source

Plugin methods return new plugin instances, so you can keep a base plugin stable and derive app-specific behavior from it. Use .configure() for existing fields, .extend*() for typed additions, and .overrideEditor() only when wrapping editor APIs or transforms that already exist. This guide maps each method to the runtime surface it changes.

On This Page

Method Map

MethodUse it forWrites to
.configure()Change existing plugin fields without widening the public type.The current plugin.
.configurePlugin()Change an existing nested plugin.A child plugin already present in plugins.
.extend()Add typed options, handlers, renderers, rules, or runtime hooks.The current plugin.
.extendPlugin()Extend a nested plugin, or add a keyed nested plugin when missing.A child plugin under plugins.
.extendSelectors()Add computed option selectors.getOption() and usePluginOption().
.extendApi()Add plugin-specific API methods.editor.api[plugin.key].
.extendEditorApi()Add editor-wide API methods.editor.api.
.extendTransforms()Add plugin-specific transforms.editor.tf[plugin.key].
.extendEditorTransforms()Add editor-wide transforms.editor.tf.
.overrideEditor()Wrap existing editor API or transform methods.editor.api and editor.tf.
.withComponent()Attach a node component to a plugin.plugin.node.component and plugin.render.node.
.clone()Copy a plugin definition.A new plugin object.

Plugin method callbacks receive the same context described in Plugin Context: editor, plugin, api, tf, getOption, getOptions, setOption, setOptions, and type.

Configure Existing Fields

Use .configure() when the plugin already has the field and you only need to change its value.

tsx
import { H1Plugin } from '@platejs/basic-nodes/react';

export const AppH1Plugin = H1Plugin.configure({
  shortcuts: {
    toggle: { keys: 'mod+alt+1' },
  },
});

Function configs run when the plugin resolves inside an editor, so they can read the current plugin options.

tsx
import { NavigationFeedbackPlugin } from 'platejs/react';

const LongerFlashPlugin = NavigationFeedbackPlugin.configure(
  ({ getOption }) => ({
    options: {
      duration: getOption('duration') + 400,
    },
  })
);

Object configs are merged with the plugin through Plate's plugin merge rules: objects merge deeply, arrays are replaced, and options are shallow merged.

<Callout type="info" title="Configure does not widen types"> `.configure()` is for existing plugin fields. If you need TypeScript to know about a new option, API method, transform, selector, handler, or renderer, use `.extend()` or the narrower `.extend*()` method. </Callout>

Configure Nested Plugins

Use .configurePlugin() when a parent plugin owns a child plugin and you want to adjust that child without replacing the whole parent.

tsx
import { createPlatePlugin } from 'platejs/react';

const CellPlugin = createPlatePlugin({
  key: 'cell',
  options: {
    padding: 12,
  },
});

export const GridPlugin = createPlatePlugin({
  key: 'grid',
  plugins: [CellPlugin],
}).configurePlugin(CellPlugin, {
  options: {
    padding: 8,
  },
});

.configurePlugin() searches nested plugins recursively. If the target plugin is not found, Plate leaves the parent unchanged.

Use .extendPlugin() when the child needs new typed behavior.

tsx
export const GridWithCellShortcutPlugin = GridPlugin.extendPlugin(CellPlugin, {
  shortcuts: {
    insertBelow: {
      keys: 'mod+enter',
      handler: ({ event }) => {
        event.preventDefault();

        return true;
      },
    },
  },
});
<Callout type="note" title="Missing nested plugins"> `.configurePlugin()` does not add a missing child. `.extendPlugin()` does: when the target key is not found, Plate adds a keyed plugin at the top level of the parent's `plugins` array and applies the extension there. </Callout>

Extend The Plugin

Use .extend() for broad plugin additions. Object extensions merge immediately; function extensions run during plugin resolution with the current editor context.

tsx
import { createPlatePlugin } from 'platejs/react';

export const MentionPlugin = createPlatePlugin({
  key: 'mention',
  node: {
    isElement: true,
    isInline: true,
  },
}).extend(({ editor }) => ({
  handlers: {
    onKeyDown: ({ event }) => {
      if (event.key === 'Escape') {
        editor.tf.deselect();
        event.preventDefault();
      }
    },
  },
  options: {
    trigger: '@',
  },
}));

Use .extend() when one extension naturally touches several plugin fields. Use the narrower methods below when the addition is specifically an API method, transform, selector, or editor override.

Selectors

Use .extendSelectors() for derived option values that components can subscribe to. Selectors are available through getOption() and React hooks such as usePluginOption().

tsx
import { type PluginConfig } from 'platejs';
import { createTPlatePlugin, usePluginOption } from 'platejs/react';

type CounterOptions = {
  value: number;
};

type CounterSelectors = {
  doubled: (factor: number) => number;
  isEven: () => boolean;
};

type CounterConfig = PluginConfig<
  'counter',
  CounterOptions,
  {},
  {},
  CounterSelectors
>;

export const CounterPlugin = createTPlatePlugin<CounterConfig>({
  key: 'counter',
  options: {
    value: 1,
  },
}).extendSelectors<CounterSelectors>(({ getOptions }) => ({
  doubled: (factor) => getOptions().value * factor,
  isEven: () => getOptions().value % 2 === 0,
}));

export function CounterValue() {
  const doubled = usePluginOption(CounterPlugin, 'doubled', 2);
  const isEven = usePluginOption(CounterPlugin, 'isEven');
  const value = usePluginOption(CounterPlugin, 'value');

  return (
    <span>
      {value} / {doubled} / {isEven ? 'even' : 'odd'}
    </span>
  );
}

Selectors are the right place for derived state. Use .extendApi() when the method is a query or utility that should not subscribe React components to option changes.

API And Transforms

Use API methods for reads and utilities. Use transforms for operations that mutate editor state.

MethodAccess pathTypical use
.extendApi()editor.api.counter.isEmpty()Plugin-specific query or utility.
.extendEditorApi()editor.api.counterLabel()Editor-wide query or utility.
.extendTransforms()editor.tf.counter.increment()Plugin-specific mutation.
.extendEditorTransforms()editor.tf.resetCounter()Editor-wide mutation.
tsx
import { type PluginConfig } from 'platejs';
import { createTPlatePlugin } from 'platejs/react';

type CounterOptions = {
  value: number;
};

type CounterPluginApi = {
  isEmpty: () => boolean;
};

type CounterEditorApi = {
  counterLabel: () => string;
};

type CounterPluginTransforms = {
  increment: () => void;
};

type CounterEditorTransforms = {
  resetCounter: () => void;
};

type CounterConfig = PluginConfig<'counter', CounterOptions>;

export const CounterPlugin = createTPlatePlugin<CounterConfig>({
  key: 'counter',
  options: {
    value: 0,
  },
})
  .extendApi<CounterPluginApi>(({ getOption }) => ({
    isEmpty: () => getOption('value') === 0,
  }))
  .extendEditorApi<CounterEditorApi>(({ getOption }) => ({
    counterLabel: () => `Count: ${getOption('value')}`,
  }))
  .extendTransforms<CounterPluginTransforms>(({ getOption, setOption }) => ({
    increment: () => setOption('value', getOption('value') + 1),
  }))
  .extendEditorTransforms<CounterEditorTransforms>(({ setOption }) => ({
    resetCounter: () => setOption('value', 0),
  }));

After the plugin resolves in an editor, call those methods from their resolved surfaces.

ts
editor.api.counter.isEmpty();
editor.api.counterLabel();
editor.tf.counter.increment();
editor.tf.resetCounter();

editor.tf is the short alias for editor.transforms; both access the same transform tree.

Override Editor Methods

Use .overrideEditor() when you need to wrap existing editor API or transform methods and keep access to the original method.

tsx
import { createPlatePlugin } from 'platejs/react';

export const LimitExclamationPlugin = createPlatePlugin({
  key: 'limitExclamation',
}).overrideEditor(({ tf: { insertText } }) => ({
  transforms: {
    insertText(text, options) {
      insertText(text === '!' ? '.' : text, options);
    },
  },
}));

The callback can override API methods, transform methods, or both.

tsx
const TrimStringPlugin = createPlatePlugin({
  key: 'trimString',
}).overrideEditor(({ api: { string } }) => ({
  api: {
    string(at, options) {
      return string(at, options).trim();
    },
  },
}));

Keep new editor methods in .extendEditorApi() or .extendEditorTransforms(). .overrideEditor() is for changing behavior while preserving the original call path.

Components

Use .withComponent() to bind a Plate UI node component to a plugin. It writes both node.component and render.node, which keeps the component available to the editor renderer and plugin metadata.

tsx
import { ParagraphPlugin } from 'platejs/react';

import { ParagraphElement } from '@/components/ui/paragraph-node';

export const AppParagraphPlugin =
  ParagraphPlugin.withComponent(ParagraphElement);

Use .withComponent() for the common one-component case. Use .extend() when the same plugin also needs render wrappers, handlers, options, or rules.

Convert Slate Plugins

Use toPlatePlugin() when you have a headless Slate plugin and need to add React-only fields such as render, handlers, or useHooks.

tsx
import { createTSlatePlugin } from 'platejs';
import { toPlatePlugin } from 'platejs/react';

import { MentionElement } from '@/components/ui/mention-node';

const BaseMentionPlugin = createTSlatePlugin({
  key: 'mention',
  node: {
    isElement: true,
    isInline: true,
  },
});

export const MentionPlugin = toPlatePlugin(BaseMentionPlugin, {
  render: {
    node: MentionElement,
  },
});

toPlatePlugin() wraps the same plugin methods, so a converted plugin can still use .configure(), .extend(), .extendApi(), .overrideEditor(), and .withComponent().

API Reference

MethodAcceptsResolution behavior
.configure(config)Object or callback returning a partial plugin config.Stores one configuration callback and applies it before function extensions.
.configurePlugin(plugin, config)Target plugin and object or callback config.Recursively configures an existing nested plugin; missing target is ignored.
.extend(config)Object or callback returning a partial plugin config.Object configs merge immediately; callback configs run during plugin resolution.
.extendPlugin(plugin, config)Target plugin and object or callback extension.Recursively extends a nested plugin; missing target is added by key.
.extendSelectors(callback)Callback returning selector functions.Extends the plugin option store selectors.
.extendApi(callback)Callback returning functions.Merges into editor.api[plugin.key] and plugin.api[plugin.key].
.extendEditorApi(callback)Callback returning functions or one-level nested function objects.Merges into editor.api and plugin.api.
.extendTransforms(callback)Callback returning functions.Merges into editor.tf[plugin.key] and plugin.transforms[plugin.key].
.extendEditorTransforms(callback)Callback returning functions or one-level nested function objects.Merges into editor.tf and plugin.transforms.
.overrideEditor(callback)Callback returning { api?, transforms? }.Deep-merges overrides into editor and plugin API/transform objects.
.withComponent(component)A node component.Sets node.component and render.node.
.clone()No arguments.Returns a merged copy of the plugin definition.

Done. Use configuration for existing fields, extension methods for new typed surface, and overrides only when the editor method already exists.