The Clipboard API, Explained
The Clipboard API lets web pages read and write the system clipboard asynchronously, replacing the old execCommand copy/paste hacks with a real API.
The Clipboard API is a browser API that lets JavaScript read from and write to the operating system’s clipboard directly, using promise-based methods like navigator.clipboard.writeText() and navigator.clipboard.readText(), instead of the older document.execCommand("copy") approach that only worked by copying an existing text selection.
Why it replaced the old copy hack
Before the Clipboard API, getting “click to copy” working meant a workaround: create a hidden, off-screen text area or input, put the text you wanted to copy into it, select that text programmatically, and call document.execCommand("copy") to copy the current selection. It worked, but it was synchronous, required a real (if invisible) DOM element, and offered no way to read the clipboard at all — only to trigger a copy of something already selected.
The Clipboard API replaces this with a proper asynchronous interface. There’s no DOM manipulation required, no hidden elements, and — critically — a symmetric way to both write to and read from the clipboard, gated by the browser’s permission model rather than only working within a user-triggered selection.
Writing to the clipboard
The simplest case is copying plain text:
async function copyText(text) {
await navigator.clipboard.writeText(text);
}
For richer content — copying an image, or copying both an HTML and plain-text representation so the user gets formatted content when pasting into a rich editor and plain text when pasting into a plain one — the API provides write(), which takes an array of ClipboardItem objects:
async function copyImage(blob) {
await navigator.clipboard.write([
new ClipboardItem({ [blob.type]: blob }),
]);
}
A ClipboardItem can bundle multiple representations of the same content under different MIME types, and the browser (and the app being pasted into) picks whichever representation it understands.
Reading from the clipboard
Reading works the same way, in reverse:
async function pasteText() {
return await navigator.clipboard.readText();
}
Reading arbitrary clipboard content — not just text — uses navigator.clipboard.read(), which returns an array of ClipboardItem objects you can inspect for the MIME types they contain and extract accordingly.
Permissions and why the browser is strict about this
Clipboard access is a genuine privacy and security surface: a page that could silently read your clipboard could scrape anything you’d recently copied — a password, a crypto wallet address, a snippet from another tab — without your knowledge. Because of that, browsers gate clipboard access more tightly than most web APIs.
Writing to the clipboard generally works from any code running in response to a direct user gesture (a click or keypress handler), without requiring an explicit permission prompt in most browsers, since the user’s own action is the signal of intent. Reading is treated more cautiously: it typically requires the page to have focus, is often subject to a clipboard-read permission check via the Permissions API, and in some contexts prompts the user explicitly the first time a page tries to read.
This is also an area where a page’s Content Security Policy and the browser’s permission model interact — clipboard access outside a secure, focused, user-initiated context will typically fail silently or throw, so production code needs to handle the rejected promise rather than assume the operation always succeeds.
Fallbacks and browser support
The Clipboard API is well supported in modern browsers, but production code that needs to work everywhere — including older browsers or restrictive embedded webviews — often still needs a fallback path. A common pattern is to try navigator.clipboard.writeText() first, and fall back to the legacy execCommand("copy") technique only if the modern API is unavailable or throws:
async function copyWithFallback(text) {
if (navigator.clipboard?.writeText) {
try {
await navigator.clipboard.writeText(text);
return;
} catch {
// fall through to the legacy path
}
}
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
textarea.style.opacity = "0";
document.body.appendChild(textarea);
textarea.select();
document.execCommand("copy");
document.body.removeChild(textarea);
}
Feature-detecting navigator.clipboard before use is worth doing regardless, since the API is only available in secure contexts (HTTPS or localhost) — another reason the broader move to HTTPS everywhere matters beyond just transport security.
Practical use cases
The most common use is the “copy to clipboard” button next to a code snippet, API key, or share link — a pattern used constantly across developer tools and documentation sites. Beyond that, the read side of the API enables features like paste-to-upload for images (letting a user paste a screenshot directly into a form rather than saving and selecting a file) and rich text editors that need to normalize pasted content from other applications.
Because clipboard events also fire independently as DOM events (copy, cut, paste) that you can listen for and intercept — for example, to strip formatting from pasted content or to transform a copied selection — the Clipboard API is often used alongside, not instead of, those existing event handlers, similar to how event delegation is used alongside direct event listeners depending on the situation.
The takeaway
The Clipboard API gives web pages an asynchronous, promise-based way to read and write the system clipboard, replacing the old execCommand-and-hidden-textarea trick with a real interface that also supports non-text content through ClipboardItem. Writing is generally permissive when triggered by a user gesture; reading is deliberately more restricted, since unrestricted clipboard access is a real privacy risk. Feature-detect before use, handle rejected promises, and keep a legacy fallback if your app needs to support older or restrictive browser environments.
Tagged
Keep reading
Takina · · 4 min read Object.groupBy() in JavaScript, Explained
Object.groupBy() buckets array items by a key you compute, replacing the reduce() boilerplate developers have written for years. How it works.
Takina · · 4 min read Web Locks API: Coordinating Work Across Browser Tabs
The Web Locks API lets JavaScript acquire named locks shared across tabs, so only one tab does work like a token refresh or a write at a time.
Takina · · 4 min read Svelte 5 Runes Explained
Svelte 5 runes like $state and $derived replace the old reactive-assignment magic with explicit function calls that work anywhere in a file.