Plate
PlateEditorsTemplates
GitHub16kGitHub
DiscordDiscord
    • Overview
    • Why This Fork
    • Examples
    • Installing Plite
    • Adding Event Handlers
    • Defining Custom Elements
    • Applying Custom Formatting
    • Executing Commands
    • Saving to a Database
    • Canonical Change Substrate
    • Improving Performance
    • Interfaces
    • Nodes
    • Locations
    • Transforms
    • Document Changes
    • Commands
    • Editor
    • Plugins
    • Rendering
    • Serializing
    • Normalizing
    • Using TypeScript
    • Roots
    • Document State
    • Editing Behavior
    • Selection And DOM
    • Clipboard And Paste
    • Decorations, annotations, and widgets
    • Schema
    • Anchor API
    • Location API
    • Path API
    • PointEntry API
    • Point API
    • Range API
    • Selection API
    • Location Types APIs
    • Span API
    • Editor
    • Element API
    • NodeEntry API
    • Node API
    • Node Types APIs
    • Text API
    • Debug Value Scrubbing
    • Transforms API
    • Plite DOM
    • History Editor API
    • History Plugin Setup
    • History
    • Plite History
    • Plite Hyperscript
    • Plite PaginationExperimental
    • Annotations
    • DOM Coverage Boundaries
    • Editable Component
    • External Text Views
    • Plite React Event Handling
    • Virtualized Rendering
    • Plite React Hooks
    • React Editor Setup
    • React Editor
    • Plite React
    • Plite Component
    • Plite Yjs
    • Plite
    • Migration
    • Contributing
    • FAQ
    • Resources

Executing Commands

PreviousNext

Extract reusable editor commands without mutating the editor object directly.

Formatting logic can live directly inside event handlers, but it gets repetitive once the same behavior is used from keyboard shortcuts, toolbar buttons, menu items, or tests. A command is reusable editor logic. Keep command reads in editor.read.<group>.<method>() or editor.read(...), and command writes in editor.update.<group>.<method>() or editor.update(...).

Extracting commands

Start by moving the bold and code-block logic into plain functions:

import { ElementApi, type Editor } from "plitejs";
 
const isBoldActive = (editor: Editor) 



























Applying Custom FormattingSaving to a Database

On This Page

Extracting commandsUsing commands from editor eventsUsing commands from UIPlugin commands
Build your editor
Production-ready AI template and reusable components.
Get all-access
=>
{
return editor.read.marks()?.bold === true;
};
const isCodeBlockActive = (editor: Editor) => {
const match = editor.read.nodes.find({
match: (node) => ElementApi.isElement(node) && node.type === "code",
});
return Boolean(match);
};
const toggleBold = (editor: Editor) => {
editor.update.marks.toggle("bold");
};
const toggleCodeBlock = (editor: Editor) => {
const isActive = isCodeBlockActive(editor);
editor.update((tx) => {
tx.nodes.set(
{ type: isActive ? "paragraph" : "code" },
{
match: (node) =>
ElementApi.isElement(node) && !tx.schema.isInline(node),
}
);
});
};
import { ElementApi, type Editor } from "plitejs";
 
const isBoldActive = (editor: Editor) => {
  return editor.read.marks()?.bold === true;
};
 
const isCodeBlockActive = (editor: Editor) => {
  const match = editor.read.nodes.find({
    match: (node) => ElementApi.isElement(node) && node.type === "code",
  });
 
  return Boolean(match);
};
 
const toggleBold = (editor: Editor) => {
  editor.update.marks.toggle("bold");
};
 
const toggleCodeBlock = (editor: Editor) => {
  const isActive = isCodeBlockActive(editor);
 
  editor.update((tx) => {
    tx.nodes.set(
      { type: isActive ? "paragraph" : "code" },
      {
        match: (node) =>
          ElementApi.isElement(node) && !tx.schema.isInline(node),
      }
    );
  });
};

These functions are not added to the editor object. They are normal JavaScript functions that receive an editor.

Using commands from editor events

Use Editable onKeyDown for keyboard shortcuts that belong to one editor UI:

import { Editable, EditorRoot, useEditor } from "plitejs/react";
 
const App = () => {
  const editor = useEditor({ initialValue });
 
  return (
    <EditorRoot editor={editor}>
      <Editable
        onKeyDown={(event, { editor }) => {
          if (event.key === "`" && event.ctrlKey) {
            event.preventDefault();
            toggleCodeBlock(editor);
            return true;
          }
 
          if (event.key === "b" && event.ctrlKey) {
            event.preventDefault();
            toggleBold(editor);
            return true;
          }
        }}
        renderElement={renderElement}
        renderLeaf={renderLeaf}
      />
    </EditorRoot>
  );
};
import { Editable, EditorRoot, useEditor } from "plitejs/react";
 
const App = () => {
  const editor = useEditor({ initialValue });
 
  return (
    <EditorRoot editor={editor}>
      <Editable
        onKeyDown={(event, { editor }) => {
          if (event.key === "`" && event.ctrlKey) {
            event.preventDefault();
            toggleCodeBlock(editor);
            return true;
          }
 
          if (event.key ===










Use plugin commands for reusable semantic behavior. This keeps the same model rule available to keyboard input, native input, toolbar logic, programmatic calls, and tests.

import {
  definePlugin,
  editorCommands,
  ElementApi,
  PointApi,
  RangeApi,
} from "plitejs";
 
const markdownBlocks = definePlugin("markdown-blocks", {
  commands: ({ around, handle }) => [
    handle(editorCommands.delete, ({ input, state }) => {
      const selection = state.selection();
 
      if (
        input.direction === "backward" &&
        input.unit === "character" &&
        selection &&
        RangeApi.isCollapsed(selection)
      ) {
        const blockEntry = state.nodes.block({ at: selection });
 
        if (blockEntry) {
          const [block, blockPath] = blockEntry;
          const start = state.points.start(blockPath);
 
          if (
            ElementApi.isElement(block) &&
            block.type !== "paragraph" &&
            start &&
            PointApi.equals(selection.anchor, start)
          ) {
            return state.transaction((tx) => {
              tx.blocks.set({ type: "paragraph" }, { at: blockPath });
            });
          }
        }
      }
 
      return false;
    }),
    around(editorCommands.insertBreak, ({ next, state }) => {
      const selection = state.selection();
 
      if (selection && RangeApi.isCollapsed(selection)) {
        const blockEntry = state.nodes.block({ at: selection });
 
        if (blockEntry) {
          const [block, blockPath] = blockEntry;
 
          if (ElementApi.isElement(block) && block.type === "heading-one") {
            const start = state.points.start(blockPath);
 
            if (start && PointApi.equals(selection.anchor, start)) {
              return next.after(
                state.transaction((tx) => {
                  tx.blocks.set({ type: "paragraph" }, { at: blockPath });
                })
              );
            }
          }
        }
      }
 
      return next();
    }),
  ],
});
 
const App = () => {
  const editor = useEditor({
    plugins: [markdownBlocks],
    initialValue,
  });
 
  return (
    <EditorRoot editor={editor}>
      <Editable renderElement={renderElement} renderLeaf={renderLeaf} />
    </EditorRoot>
  );
};
import {
  definePlugin,
  editorCommands,
  ElementApi,
  PointApi,
  RangeApi,
} from "plitejs";
 
const markdownBlocks = definePlugin("markdown-blocks", {
  commands: ({ around, handle }) => [
    handle(editorCommands.delete, ({ input, state }) => {
      const selection = state.selection();
 
      if (
        input.direction === "backward" &&
        input.unit === "character" &&
        selection &&






























































Using commands from UI

The same functions can be called from toolbar buttons:

const Toolbar = ({ editor }) => {
  return (
    <div>
      <button
        onMouseDown={(event) => {
          event.preventDefault();
          toggleBold(editor);
        }}
      >
        Bold
      </button>
      <button
        onMouseDown={(event) => {
          event.preventDefault();
          toggleCodeBlock(editor);
        }}
      >
        Code Block
      </button>
    </div>
  );
};
const Toolbar = ({ editor }) => {
  return (
    <div>
      <button
        onMouseDown={(event) => {
          event.preventDefault();
          toggleBold(editor);
        }}
      >
        Bold
      </button>
      <button
        onMouseDown={(event) => {
          event.preventDefault();
          toggleCodeBlock(editor);
        }}
      >
        Code Block



Plugin commands

Plain functions are enough for app code. Plugins can expose typed state and tx namespaces when a behavior needs to be shared across editors.

Raw Plite does not ship product commands like lists, headings, or links. Those belong in plugins or higher-level frameworks.

"b"
&&
event.ctrlKey) {
event.preventDefault();
toggleBold(editor);
return true;
}
}}
renderElement={renderElement}
renderLeaf={renderLeaf}
/>
</EditorRoot>
);
};
RangeApi.isCollapsed(selection)
) {
const blockEntry = state.nodes.block({ at: selection });
if (blockEntry) {
const [block, blockPath] = blockEntry;
const start = state.points.start(blockPath);
if (
ElementApi.isElement(block) &&
block.type !== "paragraph" &&
start &&
PointApi.equals(selection.anchor, start)
) {
return state.transaction((tx) => {
tx.blocks.set({ type: "paragraph" }, { at: blockPath });
});
}
}
}
return false;
}),
around(editorCommands.insertBreak, ({ next, state }) => {
const selection = state.selection();
if (selection && RangeApi.isCollapsed(selection)) {
const blockEntry = state.nodes.block({ at: selection });
if (blockEntry) {
const [block, blockPath] = blockEntry;
if (ElementApi.isElement(block) && block.type === "heading-one") {
const start = state.points.start(blockPath);
if (start && PointApi.equals(selection.anchor, start)) {
return next.after(
state.transaction((tx) => {
tx.blocks.set({ type: "paragraph" }, { at: blockPath });
})
);
}
}
}
}
return next();
}),
],
});
const App = () => {
const editor = useEditor({
plugins: [markdownBlocks],
initialValue,
});
return (
<EditorRoot editor={editor}>
<Editable renderElement={renderElement} renderLeaf={renderLeaf} />
</EditorRoot>
);
};
</
button
>
</div>
);
};