apps/docs/content/sdk-features/clipboard.mdx
The clipboard lets you copy, cut, and paste shapes within a single editor or between different editor instances. When you copy shapes, the editor serializes them along with their bindings and assets into a TLContent object. This format preserves document structure and relationships so shapes paste correctly elsewhere.
Clipboard operations have two flows: extracting content (copy/cut) and placing content (paste).
When you copy or cut shapes, the editor calls Editor#getContentFromCurrentPage to serialize them into a TLContent object:
const content = editor.getContentFromCurrentPage(editor.getSelectedShapeIds())
// content contains shapes, bindings, assets, and schema
This method collects the selected shapes and their descendants, gathers bindings between them, and includes any referenced assets. Root shapes (those whose parents aren't in the selection) get transformed to page coordinates so they paste at the correct position.
The method keeps only bindings where both the fromId and toId shapes are in the copied set. This prevents dangling references to shapes that won't exist in the pasted content.
Editor#putContentOntoCurrentPage handles paste operations. It takes TLContent and reconstructs shapes on the current page:
// Paste at a specific point
editor.putContentOntoCurrentPage(content, {
point: { x: 100, y: 100 },
select: true,
})
// Paste and preserve original positions
editor.putContentOntoCurrentPage(content, {
preservePosition: true,
})
The method migrates the content through the store's schema system to handle version differences, remaps shape and binding IDs to prevent collisions, and finds an appropriate parent for the pasted shapes.
The parent selection logic works like this: if shapes are selected when pasting, the editor finds the selected shape with the fewest ancestors and uses its parent. This creates intuitive behavior where pasting with a frame selected places shapes inside the frame, while pasting with shapes on the page pastes beside them. When pasting at a specific point (like the cursor position), the editor looks for an appropriate parent at that location.
The editor writes clipboard data in multiple formats. For HTML-aware applications, it embeds serialized TLContent in a <div data-tldraw> element. For plain text, it extracts text from text shapes. This multi-format approach preserves tldraw-specific data while staying compatible with other applications.
The clipboard uses a versioned format with compression. Version 3 (the current format) stores assets as plain JSON and compresses other data using LZ compression. This reduces payload size while keeping asset information quickly accessible.
When pasting, the editor tries the browser's Clipboard API first because it preserves metadata that the clipboard event API strips out. If that fails, it falls back to reading from the paste event's clipboard data. The editor handles images, files, URLs, HTML, and plain text, routing each through the appropriate handler.
Before writing to the clipboard, the editor calls Editor#resolveAssetsInContent to convert asset references into data URLs:
const content = editor.getContentFromCurrentPage(editor.getSelectedShapeIds())
const resolved = await editor.resolveAssetsInContent(content)
// resolved.assets now contain data URLs instead of asset references
This embeds images and videos directly in the clipboard data rather than relying on URLs that might not be accessible when pasting elsewhere. The resolved content becomes fully portable across editor instances.
Cut combines copy and delete. The editor first copies the selected shapes to the clipboard, then deletes the originals. This order ensures the clipboard has the data before shapes disappear, preventing data loss if the copy fails.
The TLContent type defines the clipboard payload:
interface TLContent {
shapes: TLShape[]
bindings: TLBinding[] | undefined
rootShapeIds: TLShapeId[]
assets: TLAsset[]
schema: SerializedSchema
}
shapes contains all copied shapes in serialized formrootShapeIds identifies which shapes have no parent in the copied set, distinguishing top-level shapes from nested childrenbindings holds relationships between shapes, like arrows connected to boxesassets includes images, videos, and other external resourcesschema preserves the store schema version, enabling migration when pasting content from a different editor versionEditor#putContentOntoCurrentPage offers flexible positioning:
preservePosition option places shapes at their exact stored coordinates, skipping offset calculation entirelyThe editor uses preservePosition internally when moving shapes between pages, where position preservation matters.
Shape and binding IDs get remapped during paste to prevent collisions with existing shapes. The editor creates a mapping from old IDs to new IDs, then updates all references throughout the pasted content: parent-child relationships, binding endpoints, and asset references all get the new IDs.
The preserveIds option disables remapping. This is useful when duplicating pages where you need to maintain specific IDs.
For non-tldraw content (images, URLs, plain text), use Editor#putExternalContent to route it through registered handlers:
// Paste files at a specific point
await editor.putExternalContent({
type: 'files',
files: droppedFiles,
point: { x: 100, y: 200 },
})
// Paste a URL
await editor.putExternalContent({
type: 'url',
url: 'https://example.com/image.png',
point: editor.inputs.getCurrentPagePoint(),
})
// Paste text
await editor.putExternalContent({
type: 'text',
text: 'Hello world',
point: { x: 100, y: 100 },
})
Register custom handlers with Editor#registerExternalContentHandler to customize how different content types are processed. See External content for details on the handler system.