Plate
PlateEditorsTemplates
GitHub16kGitHub
DiscordDiscord
  • Feature Kits
  • Plugin
    • Plugin Methods
    • Plugin Shortcuts
    • Plugin Context
    • Plugin Components
    • Plugin Rules
    • Editing Behavior
    • Plugin Input Rules
  • Editor
    • Editor Methods
    • Controlled Value
  • Authored Changes
  • Performance
  • Static Rendering
  • HTML
  • Markdown
  • Form
  • TypeScript
  • Debugging
  • Unit Testing
  • Browser
  • Troubleshooting
  • Locations
  • Transactions
  • Serializing
  • Roots
  • Document Meta
  • Clipboard and Paste
  • Decorations, annotations, and widgets
  • Schema
  • History
  • Pagination
  • Annotations
  • DOM Coverage
  • External Text Views
  • Virtualized Rendering

Clipboard And Paste

PreviousNext

Route copy, paste, drop, and fitted slice replacement through Plate's editor, DOM, and plugin layers.

Clipboard work crosses browser events, Plate fragments, transactions, DOM coverage, and browser proof. Use this page to decide whether a paste, copy, or drop policy belongs in EditorContent, a plugin, editor.api.dom.clipboard, or a fragment transform.

Choose the right surface

Paste bugs usually come from mixing browser event ownership with model insertion ownership.

NeedStart withOwner
One editor instance needs a local paste/drop hookplugin on.paste or on.dropplatejs/react
Document MetaDecorations, annotations, and widgets

On This Page

Choose the right surfaceRuntime pipelinePlugin clipboard policyDOM clipboard APIFragment and slice replacementHidden and projected contentBrowser proofRelated docs
Build your editor
Production-ready AI template and reusable components.
Get all-access
A reusable package owns paste/drop import policydomCommands.insertData interceptor in plugin commandsplatejs/dom
A host format needs parsing or serializationDeclare the owning plugin's codecsplatejs/dom
Framework code needs to import a DataTransfereditor.api.dom.clipboard.insertData(data)platejs/dom through platejs/react
Parsed or structural content is already decodedtx.slice.replace(slice, options?)platejs
Decoded content must fit a detached parentstate.slice.fitContent(slice, { parent, root? })platejs
Copy or drag must include hidden model contentDOM coverage copyPolicy plus model-backed clipboard dataplatejs/dom and platejs/react
The claim depends on real browser clipboard behavior@platejs/test clipboard helpers@platejs/test

Use plugin on handlers for local event interception. Use the DOM insert-data command when the behavior should apply to native paste, drop, browser tests, and every React surface that installs the plugin.

Runtime pipeline

Clipboard data enters Plate through explicit layers.

StageWhat happensOwner
Browser eventThe browser produces paste, cut, copy, dragstart, or drop with a DataTransfer.Browser
EditorContent handlerApp handlers can handle the event or let Plate continue.platejs/react
Insert-data commandTyped domCommands.insertData interceptors can claim, transform, or delegate the payload.platejs/dom
DOM clipboard importPlate reads its internal fragment, then registered host codecs, then plain text.platejs/dom
TransactionA parsed slice is fitted at the actual range and applied through one canonical replacement.platejs
Commit and renderPlate publishes one change; React renders and repairs selection.platejs and platejs/react
ProofBrowser tests assert model content, DOM/native selection where needed, focus, clipboard payload, and follow-up typing.@platejs/test

Do not close a paste bug with only a model assertion when the failure was in the browser event, DOM clipboard payload, native selection, or follow-up typing.

Plugin clipboard policy

Intercept domCommands.insertData when a feature owns a reusable DOM import rule.

import { definePlugin } from "platejs";
import { domCommands } from "platejs/dom";
 
const pasteTodoPrefix = definePlugin("paste-todo-prefix", {
  commands: ({ around }) => [
    around(domCommands.insertData, ({ input, next, state }) => {
      const text = input.getData("text/plain");
 
      if (!text.startsWith("todo:")) return next();
 
      return state.transaction((tx) => {
        tx.text.insert(text.slice("todo:".length).trim());
      });
    }),
  ],
});
import { definePlugin } from "platejs";
import { domCommands } from "platejs/dom";
 
const pasteTodoPrefix = definePlugin("paste-todo-prefix", {
  commands: ({ around }) => [
    around(domCommands.insertData, ({ input, next, state }) => {
      const text = input.getData("text/plain");
 
      if (!text.startsWith("todo:")) return next();
 
      return state.transaction((tx) => {




The interceptor receives the DataTransfer as input and returns a pure transaction spec. Return next() when Plate should keep running the internal slice, host-codec, and plain-text import path. Keep DataTransfer at the DOM boundary; headless commands start from a ContentSlice.

Use this for package-owned import rules such as custom inline syntax, pasted URLs, product fragments, and table-specific paste policy. Do not put those rules in Plate core unless the rule is part of Plate's model contract.

DOM clipboard API

React editors expose DOM clipboard helpers through editor.api.dom.clipboard.

editor.api.dom.clipboard.insertData(dataTransfer);
editor.api.dom.clipboard.insertFragmentData(dataTransfer);
editor.api.dom.clipboard.insertTextData(dataTransfer);
editor.api.dom.clipboard.readSlice(dataTransfer);
editor.api.dom.clipboard.writeSelection(dataTransfer);
editor.api.dom.clipboard.writeSlice(dataTransfer, { slice });
editor.api.dom.clipboard.insertData(dataTransfer);
editor.api.dom.clipboard.insertFragmentData(dataTransfer);
editor.api.dom.clipboard.insertTextData(dataTransfer);
editor.api.dom.clipboard.readSlice(dataTransfer);
editor.api.dom.clipboard.writeSelection(dataTransfer);
editor.api.dom.clipboard.writeSlice(dataTransfer, { slice });

Use these APIs from framework bridges, tests, or low-level event code that already has a DataTransfer. insertData owns a transaction when called directly and joins the active transaction when framework code already opened one. Command interceptors compose a transaction spec through state.

readSlice distinguishes { kind: "absent" }, malformed MIME or HTML data as { kind: "invalid", source }, and { kind: "slice", slice }. writeSlice writes one exact ContentSlice plus optional host formats. This keeps missing, invalid, and valid empty clipboard payloads distinct. Formats supplied to writeSlice are authoritative, including an intentional empty string. Installed serializers fill only formats the caller omitted.

Plate writes plain text, HTML, and an internal Plate fragment payload. The fragment payload uses application/${clipboardFormatKey}, so editors with different keys do not blindly import each other's internal JSON.

Registered host codecs add schema-aware MIME formats without putting DOM types in Plate core. A parser returns one intact ContentSlice; Plate preserves its open edge depths and detached secondary roots, then fits the complete slice against the actual paste range. Keep a codec inline in hostCodecs; name shared definitions with the HostCodec<V> type. Configuration fails for duplicate codec keys, unknown schema targets, and overlapping element/text-property claims. A codec rejects invalid external payloads with null. Well-formed slices that do not fit leave the transaction untouched and continue to the next codec or plain-text fallback. Returning a malformed slice is a codec programming error reported to the editor lifecycle error sink; the dispatcher continues to the next eligible codec without publishing a partial write.

Fragment and slice replacement

Use tx.fragment.replace(...) for known-closed content. The compiled schema fits the content at the actual target.

editor.update((tx) => {
  tx.fragment.replace([
    {
      type: "paragraph",
      children: [{ text: "Pasted paragraph" }],
    },
  ]);
});
editor.update((tx) => {
  tx.fragment.replace([
    {
      type: "paragraph",
      children: [{ text: "Pasted paragraph" }],
    },
  ]);
});

Codecs and transport boundaries preserve open edges with ContentSlice.

import { ContentSlice } from "platejs";
 
const slice = ContentSlice.fromJSON({
  content: decodedContent,
  openEnd: 1,
  openStart: 1,
  roots: {
    "note:1": decodedNote,
  },
});
 
editor.update.slice.replace(slice);
import { ContentSlice } from "platejs";
 
const slice = ContentSlice.fromJSON({
  content: decodedContent,
  openEnd: 1,
  openStart: 1,
  roots: {
    "note:1": decodedNote,
  },
});
 
editor.update.slice.replace(slice);

ContentSlice has one transport shape: { content, openStart, openEnd, roots? }. roots carries the transitive detached secondary roots referenced by the slice content. Inserting the slice remaps copied keys deterministically and keeps shared aliases together.

Core slice replacement is structural and schema-fitted. Grid-aware table paste, spreadsheet mapping, and product-specific merge rules belong in the table or product plugin that understands those structures.

When table code has a detached destination cell, call state.slice.fitContent(slice, { parent, root? }). It returns frozen, grammar-valid children or null without publishing editor state. The table plugin still owns row/column mapping, spans, and multi-cell replacement.

Hidden and projected content

Copy and drag can involve app-hidden or virtualized model content whose DOM is not mounted. DOM coverage boundaries decide whether covered content uses model serialization or is excluded. Model serialization writes the selected plain text, HTML, and Plate fragment without mounting every selected block.

Use DOM Coverage Boundaries for copyPolicy, selectionPolicy, and materialization behavior. Use Selection And DOM when a copy or paste bug also depends on caret position or native selection repair.

Browser proof

Clipboard proof should name the layer that can fail.

ClaimUseful proof
The model inserted the right contentmodel text, fragment, canonical change, and selection
The DOM payload was imported correctlybrowser clipboard helper or dispatched DataTransfer
Hidden content copied correctlycopied plain text, HTML, Plate fragment, and DOM coverage policy
Selection survived pastemodel selection, DOM/native selection where observable, and follow-up typing
A feature owns paste policyfocused DOM contribution test plus browser paste smoke

Use Browser for clipboard helpers and Editing Behavior for the full event-to-commit pipeline.

Related docs

  • EditorContent Component
  • React Editor
  • Plate DOM
  • DOM Coverage Boundaries
  • Canonical Change Substrate
  • Transforms API
tx.text.insert(text.slice("todo:".length).trim());
});
}),
],
});