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

Clipboard And Paste

PreviousNext

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

Clipboard work crosses browser events, Plite fragments, transactions, DOM coverage, and browser proof. Use this page to decide whether a paste, copy, or drop policy belongs in Editable, 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 hookEditable onPaste or onDrop
Selection And DOMDecorations, 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
plitejs/react
A reusable package owns paste/drop import policydomCommands.insertData interceptor in plugin commandsplitejs/dom
A browser MIME representation needs decoding or encodingRegister a DataTransferFormat with dataTransferFormatsplitejs/dom
Framework code needs to import a DataTransfereditor.api.dom.clipboard.insertData(data)plitejs/dom through plitejs/react
Parsed or structural content is already decodedtx.slice.replace(slice, options?)plitejs
Decoded content must fit a detached parentstate.slice.fitContent(slice, { parent, root? })plitejs
Copy or drag must include hidden model contentDOM coverage copyPolicy plus model-backed clipboard dataplitejs/dom and plitejs/react
The claim depends on real browser clipboard behavior@platejs/test clipboard helpers@platejs/test

Use Editable 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 Plite through explicit layers.

StageWhat happensOwner
Browser eventThe browser produces paste, cut, copy, dragstart, or drop with a DataTransfer.Browser
Editable handlerApp handlers can handle the event or let Plite continue.plitejs/react
Insert-data commandTyped domCommands.insertData interceptors can claim, transform, or delegate the payload.plitejs/dom
DOM clipboard importPlite reads its internal fragment, then registered DataTransfer formats, then plain text.plitejs/dom
TransactionA parsed slice is fitted at the actual range and applied through one canonical replacement.plitejs
Commit and renderPlite publishes one change; React renders and repairs selection.plitejs and plitejs/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 "plitejs";
import { domCommands } from "plitejs/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 "plitejs";
import { domCommands } from "plitejs/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 Plite should keep running the internal slice, DataTransfer-format, and plain-text import path. DataTransfer does not enter plitejs; headless code starts 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 Plite core unless the rule is part of Plite'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 transfer 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.

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

Registered DataTransfer formats add schema-aware MIME representations without putting DOM types in Plite core. Register them with dataTransferFormats(name, formats). A decoder returns one ContentSlice, or null to delegate the payload to the next format; accept returns false to skip the format before decoding. Plite preserves the slice's open edges and detached roots, then fits the complete slice at the actual insertion range.

A slice that does not fit leaves the transaction untouched, and the next format tries. Plain text is the final fallback. An encoder returns a string, or null to leave its MIME type to the next encoder; each MIME type is written once. Callback exceptions are reported to the editor's lifecycleErrorSink as data-transfer-format errors with the format key, mimeType, and phase, and cannot block a later format. During accept or decode, call context.report({ impact: "lossy", message }) to describe conversion loss. Mounted paste delivers conversion diagnostics through Editable.onPasteResult. Add a mapping or format for content the current formats cannot represent. See Plite DOM for the format contract.

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" }],
    },
  ]);
});

DataTransfer formats and transport boundaries preserve open edges with ContentSlice.

import { ContentSlice } from "plitejs";
 
const slice = ContentSlice.fromJSON({
  content: decodedContent,
  openEnd: 1,
  openStart: 1,
  roots: {
    "note:1": decodedNote,
  },
});
 
editor.update.slice.replace(slice);
import { ContentSlice } from "plitejs";
 
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 Plite 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, Plite 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 clipboard helpers for browser proof and Editing Behavior for the full event-to-commit pipeline.

Related docs

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