navigator.clipboard for real copy/paste, no execCommand
Published January 19, 2027
export const copyToClipboard = (text: string) =>
navigator.clipboard.writeText(text).then(() => true).catch(() => false);
export const pasteFromClipboard = () =>
navigator.clipboard.readText().then((text) => text).catch(() => null);
export const copyImageToClipboard = async (blob: Blob) => {
const item = new ClipboardItem({ [blob.type]: blob });
await navigator.clipboard.write([item]);
};What
navigator.clipboard reads and writes the system clipboard directly, as promises. copyToClipboard and pasteFromClipboard cover plain text; copyImageToClipboard covers raw binary data, which the old copy mechanism could never touch at all.
Why it matters
document.execCommand("copy") only ever worked by copying whatever text was currently selected in the DOM — to copy programmatic text that wasn't already visible and selected, you created an off-screen <textarea>, filled it, called .select(), ran the command, then removed the element. It was a synchronous, selection-hijacking hack, it's formally deprecated, and it had no path at all to copying an image, a table, or any binary payload — only whatever a Selection object could represent.
navigator.clipboard is a real async API: writeText/readText for plain text, and write/read for arbitrary MIME-typed data via ClipboardItem. No DOM hijacking, no synchronous requirement, and it works for content that was never rendered on the page at all — a chart exported to PNG, a generated CSV blob, anything with a Blob and a MIME type.
How it works
copyToClipboardandpasteFromClipboardboth wrap their single clipboard call in a.then()/.catch()chain rather thanasync/awaitwith atry/catch— same behavior, fewer lines, and it keeps both functions a plain expression rather than a block body.copyImageToClipboardbuilds aClipboardItemby mapping the blob's own MIME type to the blob itself —{ [blob.type]: blob }— so the same function works for a PNG, a JPEG, or an SVG blob without branching on type.navigator.clipboard.write()takes an array ofClipboardItems, even for a single item, because a single clipboard entry can offer the same content in multiple formats (e.g. bothtext/htmlandtext/plain) for whatever the paste target prefers.
Gotchas
- Every one of these calls requires a secure context, and most require the call to happen inside a user gesture (a click handler) — calling
writeTextfrom asetTimeoutor a backgroundfetch().then()will often silently reject. readText()andread()can additionally require an explicit clipboard-read permission grant, which the browser may prompt for — always handle the rejection path, don't assume the promise resolves.- Not every image MIME type is writable on every browser;
image/pngis the safe, broadly supported baseline. CheckClipboardItem.supports(mimeType)before relying on anything else.
Related
- The Web Share API — hands content to another app entirely, instead of putting it on the clipboard for the user to paste themselves.
- The File System Access API — for reading/writing files directly, when the destination is disk rather than another app's paste target.