content/docs/(guides)/plugin-shortcuts.mdx
Plugin shortcuts map key combinations to plugin methods or explicit handlers. Plate resolves shortcuts during plugin setup, stores them on editor.meta.shortcuts, and renders them through EditorHotkeysEffect inside the editable. This guide covers linked methods, custom handlers, overrides, priorities, and default shortcut ownership.
Each plugin owns a shortcuts object. At resolution time Plate namespaces every shortcut as ${plugin.key}.${shortcutName}.
When a shortcut has no handler, Plate looks for a matching plugin-specific method in this order:
editor.tf[plugin.key][shortcutName]editor.api[plugin.key][shortcutName]If neither method exists and no handler is provided, the shortcut is ignored by EditorHotkeysEffect.
| Field | Meaning |
|---|---|
keys | Key combination passed to useHotkeys. Use a string like 'mod+b' or arrays like [[Key.Mod, 'b']]. |
handler | Explicit callback receiving { editor, event, eventDetails }. |
priority | Shortcut priority. Defaults to the parent plugin priority. |
preventDefault | Passed through to useHotkeys. When omitted, Plate calls event.preventDefault() and event.stopPropagation() after handled shortcuts. |
null | Removes that named shortcut from the plugin. |
Use a linked transform when the shortcut name and plugin transform name are the same.
import { Key, createPlatePlugin } from 'platejs/react';
export const SignaturePlugin = createPlatePlugin({
key: 'signature',
})
.extendTransforms(({ editor }) => ({
insertSignature: () => {
editor.tf.insertText(' - Plate');
},
}))
.extend({
shortcuts: {
insertSignature: {
keys: [[Key.Mod, Key.Shift, 's']],
},
},
});
Pressing Mod+Shift+S calls editor.tf.signature.insertSignature().
If there is no matching transform, Plate falls back to the plugin-specific API method.
import { Key, createPlatePlugin } from 'platejs/react';
export const InspectPlugin = createPlatePlugin({
key: 'inspect',
})
.extendApi(({ editor }) => ({
logText: () => {
editor.api.debug.info('Editor text', editor.api.string([]));
},
}))
.extend({
shortcuts: {
logText: {
keys: [[Key.Mod, Key.Alt, 'l']],
},
},
});
Pressing Mod+Alt+L calls editor.api.inspect.logText().
Use a handler when the shortcut needs the keyboard event, custom branching, or work that should not live as a plugin API/transform method.
import { Key, createPlatePlugin } from 'platejs/react';
export const DraftPlugin = createPlatePlugin({
key: 'draft',
}).extend({
shortcuts: {
saveDraft: {
keys: [[Key.Mod, 's']],
handler: ({ editor }) => {
const text = editor.api.string([]);
if (text.trim().length === 0) return false;
editor.api.debug.info('Draft text', text);
return true;
},
},
},
});
Returning false means "not handled"; Plate will not call preventDefault() for that key press. Returning true or undefined means handled when preventDefault is omitted.
Plate has two layers of default-prevention behavior:
| Configuration | Behavior |
|---|---|
preventDefault omitted and handler returns anything except false | Plate calls event.preventDefault() and event.stopPropagation(). |
Handler returns false | Plate leaves the event alone. |
preventDefault is set | Plate passes the option to useHotkeys and skips its own preventDefault() call. |
Use the default omission for normal editor commands. Set preventDefault only when you intentionally want useHotkeys to own that behavior.
Configure a named shortcut to change its keys.
import { BoldPlugin } from '@platejs/basic-nodes/react';
import { Key } from 'platejs/react';
export const AppBoldPlugin = BoldPlugin.configure({
shortcuts: {
toggle: {
keys: [[Key.Mod, Key.Shift, 'b']],
},
},
});
Set a shortcut to null to remove it.
import { ItalicPlugin } from '@platejs/basic-nodes/react';
export const AppItalicPlugin = ItalicPlugin.configure({
shortcuts: {
toggle: null,
},
});
The null value removes italic.toggle from editor.meta.shortcuts.
A plugin can declare multiple shortcut names. Keep each name aligned with the method it should call.
import { Key, createPlatePlugin } from 'platejs/react';
export const ReviewPlugin = createPlatePlugin({
key: 'review',
})
.extendTransforms(({ editor }) => ({
accept: () => editor.tf.insertText('Accepted'),
reject: () => editor.tf.insertText('Rejected'),
}))
.extend({
shortcuts: {
accept: {
keys: [[Key.Mod, Key.Alt, 'a']],
},
reject: {
keys: [[Key.Mod, Key.Alt, 'r']],
},
},
});
This creates review.accept and review.reject in editor.meta.shortcuts.
Shortcut priority defaults to the parent plugin priority. Set priority on a shortcut when two handlers use the same key combination and one should win.
import { createPlatePlugin } from 'platejs/react';
export const PriorityPlugin = createPlatePlugin({
key: 'priority',
priority: 20,
}).extend({
shortcuts: {
openCommandMenu: {
keys: 'mod+k',
priority: 200,
handler: ({ editor }) => {
editor.api.debug.info('Open command menu');
return true;
},
},
},
});
Plate stores the resolved priority with the shortcut and passes it to useHotkeys.
createPlateEditor({ shortcuts }) attaches shortcuts to the root plugin. Use it for editor-wide commands that do not belong to one feature plugin.
import { createPlateEditor } from 'platejs/react';
export const editor = createPlateEditor({
shortcuts: {
reportWordCount: {
keys: 'mod+shift+w',
handler: ({ editor }) => {
const words = editor.api.string([]).trim().split(/\s+/).filter(Boolean);
editor.api.debug.info('Word count', words.length);
return true;
},
},
},
});
Internally this becomes a root shortcut, so plugin-owned shortcuts are still the better fit for feature-owned behavior.
| Plugin | Shortcut name | Keys |
|---|---|---|
BoldPlugin | toggle | Mod+B |
ItalicPlugin | toggle | Mod+I |
UnderlinePlugin | toggle | Mod+U |
ParagraphPlugin | toggleParagraph | Mod+Alt+0, Mod+Shift+0 |
CopilotPlugin | accept | Tab |
CopilotPlugin | reject | Escape |
Other plugins often expose toggle, insert, or feature-specific transforms without default keys. Add shortcuts in your app when those commands should be keyboard-accessible.
type Shortcut = HotkeysOptions & {
keys?: Keys | null;
priority?: number;
handler?: (ctx: {
editor: PlateEditor;
event: KeyboardEvent;
eventDetails: HotkeysEvent;
}) => boolean | void;
};
Done. Name shortcuts after plugin-specific transforms or API methods by default, and use handlers only when the keyboard event is part of the behavior.