From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
UNPUBLISHED
  • Overview
  • Why This Fork
  • Examples
Walkthroughs
  • Installing Plite
  • Adding Event Handlers
  • Defining Custom Elements
  • Applying Custom Formatting
  • Executing Commands
  • Saving to a Database
  • Canonical Change Substrate
  • Improving Performance
Concepts
  • Interfaces
  • Nodes
  • Locations
  • Transforms
  • Document Changes
  • Commands
  • Editor
  • Extensions
  • Rendering
  • Serializing
  • Normalizing
  • Using TypeScript
  • Roots
  • Document State
  • Editing Behavior
  • Selection And DOM
  • Clipboard And Paste
  • Projection And Overlays
  • Schema
API
  • 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
Libraries
  • Plite DOM
  • History Editor API
  • History Extension Setup
  • History
  • Plite History
  • Plite Hyperscript
  • Plite Layout
  • Annotations
  • DOM Coverage Boundaries
  • Editable Component
  • Plite React Event Handling
  • Virtualized Rendering
  • Plite React Hooks
  • React Editor Setup
  • React Editor
  • Plite React
  • Plite Component
  • Plite Yjs
  • Plite
General
  • Migration
  • Contributing
  • Docs Proof Map
  • FAQ
  • Resources

Clipboard And Paste

PreviousNext

Route copy, paste, drop, and fitted slice replacement through Plite's editor, DOM, and extension 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, an extension, 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@platejs/plite-react
Selection And DOMProjection And Overlays

On This Page

Choose The Right SurfaceRuntime PipelineExtension 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 policyextension contribution clipboardHandler(...)@platejs/plite-dom
A host format needs parsing or serializationInline a codec in hostCodecs@platejs/plite-dom
Framework code needs to import a DataTransfereditor.api.dom.clipboard.insertData(data)@platejs/plite-dom through @platejs/plite-react
Parsed or structural content is already decodedtx.slice.replace(slice, options?)@platejs/plite
Decoded content must fit a detached parentstate.slice.fitContent(slice, { parent, root? })@platejs/plite
Copy or drag must include hidden model contentDOM coverage copyPolicy plus model-backed clipboard data@platejs/plite-dom and @platejs/plite-react
The claim depends on real browser clipboard behavior@platejs/browser clipboard helpers@platejs/browser

Use Editable for local event interception. Use a DOM clipboard handler when the behavior should apply to native paste, drop, browser tests, and every React surface that installs the extension.

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.@platejs/plite-react
Clipboard handlersDescriptor-owned clipboardHandler(...) contributions can claim the payload.@platejs/plite-dom
DOM clipboard importPlite reads its internal fragment, then registered host codecs, then plain text.@platejs/plite-dom
TransactionA parsed slice is fitted at the actual range and applied through one canonical replacement.@platejs/plite
Commit and renderPlite publishes one change; React renders and repairs selection.@platejs/plite and @platejs/plite-react
ProofBrowser tests assert model content, DOM/native selection where needed, focus, clipboard payload, and follow-up typing.@platejs/browser

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.

Extension Clipboard Policy

Use clipboardHandler(...) when a feature owns a reusable DOM import rule.

import { defineExtension } from "@platejs/plite";
import { clipboardHandler } from "@platejs/plite-dom";
 
const pasteTodoPrefix = defineExtension("paste-todo-prefix", {
  contributions: [
    clipboardHandler({
      insertData(data, { next, transaction }) {
        const text = data.getData("text/plain");
 
        if (!text.startsWith("todo:")) {
          return next();
        }
 
        transaction.text.insert(text.slice("todo:".length).trim());
 
        return true;
      },
    }),
  ],
});
import { defineExtension } from "@platejs/plite";
import { clipboardHandler } from "@platejs/plite-dom";
 
const pasteTodoPrefix = defineExtension("paste-todo-prefix", {
  contributions: [
    clipboardHandler({
      insertData(data, { next, transaction }) {
        const text = data.getData("text/plain");
 
        if (!text.startsWith("todo:")) {
          return next();
        }
 
        transaction.text.insert(text.slice("todo:".





The handler receives the transaction owned by DOM clipboard ingress. Return true when the extension handled the payload. Return next() when Plite should keep running the exact-slice and plain-text import path in that transaction. DataTransfer does not enter @platejs/plite; 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. Clipboard handlers mutate through the supplied transaction.

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.

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 host codecs add schema-aware MIME formats without putting DOM types in Plite core. A parser returns one intact ContentSlice; Plite 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; use defineHostCodec only when the same codec is reused. 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/plite";
 
const slice = ContentSlice.fromJSON({
  content: decodedContent,
  openEnd: 1,
  openStart: 1,
  roots: {
    "note:1": decodedNote,
  },
});
 
editor.update.slice.replace(slice);
import { ContentSlice } from "@platejs/plite";
 
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 extension 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 extension still owns row/column mapping, spans, and multi-cell replacement.

Hidden And Projected Content

Copy and drag can involve model content whose DOM is hidden, staged, or virtualized. DOM coverage boundaries decide whether hidden content participates in copy, find, and selection conversion.

Use DOM Coverage Boundaries for copyPolicy, findPolicy, 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 Browser for clipboard helpers 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
length
).
trim
());
return true;
},
}),
],
});