API in Capabilities
Async Clipboard API
Published
The Async Clipboard API, navigator.clipboard, reads and writes the system clipboard through promises, replacing document.execCommand('copy') and ('paste'). Text goes through writeText() and readText(); images, HTML, and multi-format payloads travel as ClipboardItem objects through write() and read().
Chrome 66 shipped writeText() and readText(), and Chrome 76 added read(), write(), and ClipboardItem; Edge 79 and Samsung Internet 12.0 follow Chromium. Firefox added writeText() in 63, readText() in 125, and read(), write(), and ClipboardItem in 127. Safari 13.1 on macOS and 13.4 on iOS shipped all four methods at once (BCD api.Clipboard). The API exists only in a Window on a secure context: on http:// origins other than localhost, navigator.clipboard is undefined, and Chromium rejects reads from workers with NotAllowedError.
Syntax
Section titled “Syntax”navigator.clipboard.readText()navigator.clipboard.read()navigator.clipboard.read(formats)navigator.clipboard.writeText(data)navigator.clipboard.write(data)
new ClipboardItem(items)new ClipboardItem(items, options)clipboardItem.getType(type)ClipboardItem.supports(type)readText() returns Promise<DOMString>; read() returns Promise<sequence<ClipboardItem>> whose items carry type names only, with the bytes fetched when getType() is called; writeText() and write() return Promise<undefined>. Reads need transient user activation in Firefox 125+ and Safari 13.1+, and either activation or the clipboard-read permission in Chrome. Writes need transient activation in Firefox 63+ and Safari 13.1+, and since Chrome 107 either activation or the clipboard-write permission (before 107 the permission alone decided). ClipboardItem.supports() is static and synchronous; it exists in Chrome 121, Firefox 127, and Safari 18.4.
Parameters
Section titled “Parameters”The three method arguments and the two ClipboardItem constructor arguments cover every input the specification defines.
| Parameter | Type | Required | Description |
|---|---|---|---|
data (writeText) |
DOMString |
Yes | Written as a text/plain;charset=utf-8 blob. On Windows the browser replaces \n with \r\n first. |
data (write) |
sequence<ClipboardItem> |
Yes | Chrome 76+ and Firefox 127+ accept exactly one item; an array with two or more items rejects with NotAllowedError (BCD api.Clipboard.write). |
formats (read) |
ClipboardUnsanitizedFormats |
No | One member, unsanitized: sequence<DOMString>, asking for raw rather than sanitized bytes. Chrome 122 honours it for text/html only; any other type rejects. |
items (constructor) |
record<DOMString, ClipboardItemData> |
Yes | MIME type to Promise<Blob or DOMString> (a plain Blob or string is wrapped). Keys prefixed with "web " are custom formats (Chrome 104). |
options.presentationStyle |
"unspecified", "inline", or "attachment" |
No | Default "unspecified"; a hint to paste targets. Chrome ignores it; Firefox 127 and Safari 13.1 expose it on read items. |
type (getType, supports) |
DOMString |
Yes | A MIME type, optionally with the "web " prefix. |
A ClipboardItem exposes types (a frozen array of its MIME type strings) and presentationStyle. The mandatory types every browser must accept are text/plain, text/html, and image/png; image/svg+xml is optional and ships in Chrome 124.
Exceptions
Section titled “Exceptions”Every method returns a rejected promise rather than throwing, except the ClipboardItem constructor and the synchronous part of getType().
| Exception | Condition |
|---|---|
NotAllowedError |
read() or readText() when “check clipboard read permission” fails (no transient activation and no granted clipboard-read); write() or writeText() when the write check fails; a blob whose type is outside the mandatory and optional types; sanitization that did not complete; a representation promise that rejected; read(formats) naming an unsanitized type outside the optional unsanitized list; more than one ClipboardItem in Chrome and Firefox; the document not having focus in Chromium; a clipboard-read or clipboard-write Permissions Policy denial. |
NotFoundError |
readText() when the clipboard holds no text/plain representation, or decoding that representation fails. |
TypeError |
new ClipboardItem() with an empty items record, a key that does not parse as a MIME type, or the same type listed twice; getType() with an unparsable type. |
InvalidStateError |
getType() on an item from read() after the system clipboard changed, or on an item whose originating system entry is gone. |
DataError |
Chromium only: a ClipboardItemData promise that could not be read or decoded, or clipboard contents that changed while a paste event was being handled. |
Firefox adds a browser-level gate the specification does not name: read() and readText() show a paste prompt the user must accept, suppressed only when the clipboard content is same-origin (BCD api.Clipboard.read). A declined prompt rejects with NotAllowedError.
Examples
Section titled “Examples”Each example feature-detects the exact method it needs and shows the branch taken when it is missing. Run the write examples from a click handler so transient activation is still valid.
Copying text with a hidden-textarea fallback
Section titled “Copying text with a hidden-textarea fallback”navigator.clipboard.writeText() covers Chrome 66+, Firefox 63+, and Safari 13.1+; everything older, and any http:// page, needs the deprecated document.execCommand('copy'), which still works in those engines for plain text only.
async function copyText(text) { if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(text); return 'async'; } const area = document.createElement('textarea'); area.value = text; area.setAttribute('readonly', ''); area.style.position = 'fixed'; area.style.opacity = '0'; document.body.append(area); area.select(); const ok = document.execCommand('copy'); area.remove(); return ok ? 'legacy' : 'failed';}
document.querySelector('#copy').addEventListener('click', async () => { const result = await copyText(location.href); console.log(`copied via ${result}`);});The function returns which path ran so the UI can say “Copied” only when one of them succeeded; execCommand returns false instead of rejecting when the browser refuses.
Writing a PNG and an HTML fallback in one item
Section titled “Writing a PNG and an HTML fallback in one item”A single ClipboardItem can carry several representations; the paste target picks the richest one it understands. Check ClipboardItem.supports() where it exists (Chrome 121, Firefox 127, Safari 18.4) and treat its absence as “mandatory types only”.
async function copyChart(canvas, captionHtml) { if (!navigator.clipboard?.write || !('ClipboardItem' in window)) { return copyText(captionHtml.replace(/<[^>]+>/g, '')); } const png = new Promise((resolve) => canvas.toBlob(resolve, 'image/png')); const items = { 'image/png': png }; const htmlOk = typeof ClipboardItem.supports !== 'function' || ClipboardItem.supports('text/html'); if (htmlOk) { items['text/html'] = new Blob([captionHtml], { type: 'text/html' }); } try { await navigator.clipboard.write([new ClipboardItem(items)]); } catch (err) { if (err.name !== 'NotAllowedError') throw err; return copyText(captionHtml.replace(/<[^>]+>/g, '')); }}Passing the toBlob promise directly, rather than awaiting it first, lets the browser start the write inside the same gesture and fill in the bytes later; Safari 13.1 is the engine that depends on this ordering (web.dev, Unblocking clipboard access).
Reading an image from the clipboard without a Chromium-only permission query
Section titled “Reading an image from the clipboard without a Chromium-only permission query”Only Chromium recognises clipboard-read as a Permissions API name; navigator.permissions.query({ name: 'clipboard-read' }) throws TypeError in Firefox and Safari. Skip the query, call read() inside the gesture, and branch on the rejection.
document.querySelector('#paste').addEventListener('click', async () => { if (!navigator.clipboard?.read) { document.querySelector('#paste-area').focus(); // let the user press Ctrl+V / Cmd+V return; } try { const items = await navigator.clipboard.read(); const item = items.find((i) => i.types.includes('image/png')); if (!item) { console.log('no PNG on the clipboard; types were', items.flatMap((i) => i.types)); return; } const blob = await item.getType('image/png'); document.querySelector('#preview').src = URL.createObjectURL(blob); } catch (err) { if (err.name === 'NotAllowedError') { document.querySelector('#paste-area').focus(); return; } throw err; }});Focusing a contenteditable or textarea and listening for the paste event is the portable fallback: the paste event’s clipboardData.files delivers the same PNG in every engine without any permission.
See also
Section titled “See also”- Web Share API, where a clipboard write is the usual fallback
- File System Access API, for files too large to round-trip through the clipboard
- Clipboard API and events: write() method (w3.org)
- Unblocking clipboard access (web.dev)
- Async Clipboard API (webkit.org)
- Chrome Platform Status: Asynchronous Clipboard API (chromestatus.com)
Specifications
| Specification | Status |
|---|---|
| Async Clipboard API | W3C draft |
| Clipboard API and events: read() method | W3C |
| Clipboard API and events: write() method | W3C |
- Legend
- Yes
- Partial
- Flag
- No
- Unknown
| Browser / Platform | Support | Versions | Confidence | Source | Notes |
|---|---|---|---|---|---|
| Chrome (Desktop) | Yes | 76 | high | source | 1 |
| Chrome (Android) | Yes | 76 | high | source | 23 |
| Edge (Desktop) | Yes | 79 | high | source | 45 |
| Firefox (Desktop) | Yes | 127 | high | source | 678 |
| Firefox (Android) | Yes | 127 | high | source | 9101112 |
| Safari (macOS) | Yes | 13.1 | high | source | — |
| Safari (iOS) | Yes | 13.4 | high | source | 13 |
| Samsung Internet | Yes | 12.0 | high | source | 1415 |
| WebView (Android) | Yes | 76 | high | source | 1617 |
- The user must grant the `clipboard-read` permission.
- The user must grant the `clipboard-read` permission.
- Derived by browser-compat-data mirroring from Chrome.
- The user must grant the `clipboard-read` permission.
- Derived by browser-compat-data mirroring from Chrome.
- This method must be called within user gesture event handlers.
- A paste prompt is displayed when the clipboard is read. If the clipboard contains same-origin content, the prompt is suppressed.
- Web extensions with the `clipboardRead` permission in their manifest can read data without the paste prompt displaying. Prior to Firefox 147, extensions without this permission were unable to read clipboard data.
- This method must be called within user gesture event handlers.
- A paste prompt is displayed when the clipboard is read. If the clipboard contains same-origin content, the prompt is suppressed.
- Web extensions with the `clipboardRead` permission in their manifest can read data without the paste prompt displaying. Prior to Firefox 147, extensions without this permission were unable to read clipboard data.
- Derived by browser-compat-data mirroring from Firefox.
- Derived by browser-compat-data mirroring from Safari.
- The user must grant the `clipboard-read` permission.
- Derived by browser-compat-data mirroring from Chrome Android.
- The user must grant the `clipboard-read` permission.
- Derived by browser-compat-data mirroring from Chrome Android.