tldraw/tldraw · warning · Error
Failed to copy
Error message
Failed to copy
What it means
Thrown on the SVG fallback copy path in copyAs when navigator.clipboard.write is unavailable (so the code falls into the format switch) and editor.getSvgString(ids, imageOpts) returns null. A null result means the editor could not produce an SVG for the given selection, so there is nothing to write to the clipboard and the copy aborts.
Source
Thrown at packages/tldraw/src/lib/utils/export/copyAs.ts:72
const { blobPromise, mimeType } = exportToImagePromiseForClipboard(editor, ids, imageOpts)
const types: Record<string, Promise<Blob>> = { [mimeType]: blobPromise }
const additionalMimeType = getAdditionalClipboardWriteType(opts.format)
if (additionalMimeType && doesClipboardSupportType(additionalMimeType)) {
types[additionalMimeType] = blobPromise.then((blob) =>
FileHelpers.rewriteMimeType(blob, additionalMimeType)
)
}
return clipboardWrite(types)
}
switch (opts.format) {
case 'svg': {
return fallbackWriteTextAsync(async () => {
const result = await editor.getSvgString(ids, imageOpts)
if (!result) throw new Error('Failed to copy')
return result.svg
})
}
case 'png':
throw new Error('Copy not supported')
default:
exhaustiveSwitchError(opts.format)
}
}
async function fallbackWriteTextAsync(getText: () => Promise<string>) {
await navigator.clipboard?.writeText?.(await getText())
}
View on GitHub (pinned to b31086b447)
Solutions
- Guard the call: only copy when ids.length > 0 and editor.canExportShapes(ids) (or check getSvgString result) is truthy.
- Ensure every custom ShapeUtil implements toSvg / the SVG export path.
- If clipboard.write is available, prefer it (it produces a proper image/svg+xml ClipboardItem) instead of the text fallback.
- Show a user-facing message when getSvgString returns null rather than letting the throw surface.
Example fix
// before: unconditional copy
await copyAs(editor, ids, { format: 'svg' })
// after: guard before copying
if (ids.length === 0) return
const svg = await editor.getSvgString(ids, { format: 'svg' })
if (!svg) {
showToast('Nothing to copy')
return
}
await copyAs(editor, ids, { format: 'svg' }) Defensive patterns
Strategy: validation
Validate before calling
// Pre-flight: confirm SVG export will succeed before copying.
if (ids.length === 0) return
const preview = await editor.getSvgString(ids, { format: 'svg' })
if (!preview) {
notifyUser('Nothing to copy as SVG.')
return
}
await copyAs(editor, ids, { format: 'svg' }) Type guard
function canExportSvg(editor: Editor, ids: TLShapeId[]): boolean {
return ids.length > 0 && ids.every(id => {
const util = editor.getShapeUtil(editor.getShape(id)!.type)
return typeof util.toSvg === 'function'
})
} Try / catch
try {
await copyAs(editor, ids, { format: 'svg' })
} catch (e: any) {
if (/Failed to copy/i.test(e.message)) {
notifyUser('Could not copy the selection as SVG.')
} else throw e
} Prevention
- Disable the copy-as-SVG action when the selection is empty.
- Implement toSvg on every custom ShapeUtil you author.
- Prefer the clipboard.write path (async ClipboardItem) when available.
When it happens
Trigger: Calling copyAs(editor, ids, { format: 'svg' }) in a browser without async ClipboardItem.write support (falls into the switch), with an ids list that is empty or contains only shapes whose ShapeUtil does not implement toSvg (or shapes that have no renderable geometry), so getSvgString resolves to null.
Common situations: User triggers copy-as-SVG with nothing selected; the selection contains only shapes (e.g. a custom shape) whose util has no getSvg/toSvg method; assets referenced by the shapes failed to load so SVG export bails; running in a browser/iframe where clipboard.write is missing.
Related errors
- Could not create SVG
- Copy not supported
- Could not construct SVG.
- Could not construct image.
- mermaid diagram error: ${parsedResult.diagramType}
AI-assisted analysis of tldraw/tldraw@b31086b447 (2026-08-12).
Data as JSON: /api/errors/07bdcacc4775cd6d.
Report an issue: GitHub.