Skip to content

Async Clipboard API: reading and writing the clipboard

In one line: The Async Clipboard API — navigator.clipboard — lets you read from and write to the system clipboard programmatically. Writing plain text requires no permission; reads are tightly gated: Chromium may use the clipboard-read permission, while Firefox and Safari require transient activation and the browser’s own paste prompt (no persistent grant is stored). All operations require a secure context (HTTPS or localhost).

try {
await navigator.clipboard.writeText('Hello, clipboard!');
} catch (err) {
console.error('Clipboard write failed:', err);
}

writeText requires:

  • A secure context (HTTPS or localhost).
  • Either a user activation or that the page has focus (the exact rule is browser-dependent, but calling from a click handler is always safe).
const blob = new Blob(['<b>hello</b>'], { type: 'text/html' });
const item = new ClipboardItem({ 'text/html': blob });
await navigator.clipboard.write([item]);

write() accepts an array of ClipboardItem objects. Each item can carry multiple MIME types so the paste target can pick the richest format it understands.

const text = await navigator.clipboard.readText();
const items = await navigator.clipboard.read();
for (const item of items) {
for (const type of item.types) {
const blob = await item.getType(type);
// process blob
}
}

Permission behavior differs significantly by browser family.

Chromium uses the clipboard-read and clipboard-write Permissions API names:

  • Writing (writeText / write): no permission prompt for writeText when called from a focused, user-activated context. write() with certain MIME types (e.g., images) may prompt for clipboard-write.
  • Reading (readText / read): requires the clipboard-read permission; a browser prompt is shown the first time. You can query the current state without triggering a prompt:
// Chromium only — clipboard-read is not a recognised name in Firefox or Safari
const { state } = await navigator.permissions.query({ name: 'clipboard-read' });
// state: 'granted' | 'denied' | 'prompt'

Firefox does not recognise clipboard-read or clipboard-write as Permissions API names — navigator.permissions.query({ name: 'clipboard-read' }) will reject or return a nonsense state. Instead:

  • Writing: requires transient activation (a user gesture such as a click).
  • Reading: requires transient activation and triggers a one-time browser paste confirmation dialog the user must accept; there is no persistent grant.

Safari also does not expose clipboard-read/clipboard-write to the Permissions API. Its model is similar to Firefox:

  • Writing: requires transient activation.
  • Reading: requires transient activation and a user-initiated paste prompt; no persistent permission is stored.

navigator.clipboard is only available in secure contexts (HTTPS or localhost). On plain HTTP the property is undefined.

Both read and write operations should be called from within a user-gesture handler or while the document has focus. Browsers differ slightly, but calling from a click handler is always safe. Attempting to call outside a user-activated context typically results in a NotAllowedError.

The legacy document.execCommand('copy') and document.execCommand('paste') work in older browsers but are deprecated and do not support rich content. Use them only as a last resort:

function legacyCopy(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
textarea.remove();
}

See /compatibility/ for current per-browser data.

Decision question Recommended action Rationale
Copy plain text on button click? navigator.clipboard.writeText() inside a click handler. No permission prompt; works in all modern browsers with HTTPS.
Copy an image or HTML to clipboard? navigator.clipboard.write([new ClipboardItem(...)]). Allows multi-format payloads; the paste target picks the best type.
Read clipboard contents? Always call from a user-gesture handler. In Chromium you can query clipboard-read permission first; in Firefox and Safari rely on transient activation and the browser’s own paste prompt — do not use navigator.permissions.query for this. Permission model is browser-family-specific; transient activation is the portable baseline.
Need to support older browsers? Feature-detect navigator.clipboard; fall back to execCommand. execCommand is deprecated but still works in older engines.
Clipboard in a Service Worker or background context? Not possible — clipboard access requires a focused document. By spec and browser policy, clipboard is not available in background contexts.
  • Serve the page over HTTPS — navigator.clipboard is undefined on plain HTTP.
  • Call clipboard methods from user-gesture handlers or while the document has focus.
  • Use writeText for plain text; use write with ClipboardItem for rich content.
  • Always call read methods from a user-gesture handler. In Chromium, optionally query the clipboard-read permission state first; in Firefox and Safari, depend on transient activation and the browser’s paste prompt — navigator.permissions.query({ name: 'clipboard-read' }) is not portable across browser families.
  • Catch errors from all clipboard calls — permissions can be denied or revoked.
  • Provide a document.execCommand fallback for browsers that do not support the Async Clipboard API.