Back to Tldraw

Clipboard

apps/docs/content/sdk-features/clipboard.mdx

5.4.08.7 KB
Original Source

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.

How clipboard operations work

Clipboard operations have two flows: extracting content (copy/cut) and placing content (paste).

Extracting content

When you copy or cut shapes, the editor calls Editor#getContentFromCurrentPage to serialize them into a TLContent object (or undefined if the selection is empty):

ts
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.

Placing content

Editor#putContentOntoCurrentPage handles paste operations. It takes TLContent and reconstructs shapes on the current page:

ts
// 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 (it throws if content.schema is missing), remaps shape and binding IDs to prevent collisions, and finds an appropriate parent for the pasted shapes.

Parent selection depends on how you paste. With a point, the editor uses the deepest shape under that point that can receive every pasted root shape (via ShapeUtil#canReceiveNewChildrenOfType). Without a point, it looks at the current selection: for each selected shape it takes the nearest container that accepts the content (the shape itself, an accepting ancestor, or its parent), and if the selection spans several containers it uses their deepest common accepting ancestor. A shape is never pasted into itself. Shapes that land on the page are then reparented into any frame that contains their center.

Browser clipboard integration

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 the text of the copied shapes.

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 keeps the payload small while asset information stays quickly accessible. Older version 1 and 2 payloads are still read on paste.

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, and it prefers the event's files when the API only returned file names. The editor handles images, files, URLs, HTML, and plain text, routing each through the appropriate handler. To copy shapes as an image instead, use copyAs, which shares the same pipeline.

Three hooks on TldrawOptions let you intercept these flows. onClipboardPasteRaw fires before tldraw parses the clipboard, so you can read the raw ClipboardEvent yourself; return false to short-circuit the default pipeline. onBeforePasteFromClipboard receives the parsed external content and can transform it or return false to cancel. onBeforeCopyToClipboard receives the TLContent about to be written and can transform it or return false to cancel the copy (for a cut, nothing is deleted).

Asset resolution

Before writing to the clipboard, the editor calls Editor#resolveAssetsInContent. Image and video assets whose src isn't already a data or http URL (for example, assets stored in IndexedDB) are resolved and inlined as data URLs; hosted URLs are copied unchanged:

ts
const content = editor.getContentFromCurrentPage(editor.getSelectedShapeIds())
const resolved = await editor.resolveAssetsInContent(content)
// local assets in resolved.assets now have data URLs for src

This makes the content portable across editor instances without relying on URLs that only work in the source app.

Cut operations

Cut combines copy and delete. The editor first copies the selected shapes to the clipboard, then deletes the originals, so a failed copy leaves your shapes intact.

Plain text paste

Cmd+Shift+V (or Ctrl+Shift+V on Windows and Linux) pastes the clipboard as plain text. HTML and rich formatting are stripped. This is the standard "paste without formatting" shortcut. It's handy when styled text from a browser or word processor would otherwise bring its fonts and colors onto the canvas with it. If the clipboard has no plain text (a copied PNG, say), the shortcut falls through to the normal paste.

Content structure

The TLContent type defines the clipboard payload:

ts
interface TLContent {
	shapes: TLShape[]
	bindings: TLBinding[] | undefined
	rootShapeIds: TLShapeId[]
	assets: TLAsset[]
	schema: SerializedSchema
	users?: TLUser[]
}
  • shapes contains all copied shapes in serialized form
  • rootShapeIds identifies which shapes have no parent in the copied set, distinguishing top-level shapes from nested children
  • bindings holds relationships between shapes, like arrows connected to boxes
  • assets includes images, videos, and other external resources
  • schema preserves the store schema version, so content from a different editor version can be migrated on paste
  • users carries any user records referenced by the copied shapes, so attribution display names survive the paste; on paste, only users not already in the store are created

Position handling

Editor#putContentOntoCurrentPage offers flexible positioning:

  • By default, shapes paste in place if any of them is on screen; otherwise the group is centered in the viewport
  • When pasting into a selected container, shapes are centered in that container
  • With the point option, shapes are centered on that point. The UI passes the cursor position when the paste-at-cursor preference is on, when you paste from the context menu, or when you press Cmd+Option+V (Ctrl+Alt+V), which inverts the preference for one paste
  • The preservePosition option places shapes at their exact stored coordinates

The editor uses preservePosition internally when moving shapes between pages, where position preservation matters.

ID remapping

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 parent-child relationships and binding endpoints to match. Asset IDs are not remapped: assets already in the store are reused, and new ones are created under their original IDs.

The preserveIds option disables remapping. The editor uses it when moving shapes between pages, where the shapes should keep their existing IDs.

External content handling

For non-tldraw content (images, URLs, plain text), use Editor#putExternalContent to route it through registered handlers:

ts
// 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 },
})

Serialized tldraw content goes through the same route: editor.putExternalContent({ type: 'tldraw', content }) marks a history stopping point, pastes with Editor#putContentOntoCurrentPage, and selects the result. Register custom handlers with Editor#registerExternalContentHandler to customize how different content types are processed. See External content for details on the handler system.

  • Custom paste behavior shows how to customize paste by registering an external content handler that changes where pasted shapes are positioned.
  • External content sources shows how to handle different content types when pasting into tldraw, including custom handling for HTML content.