# Async Clipboard API

> navigator.clipboard read(), readText(), write(), and writeText(), ClipboardItem members, every DOMException the spec defines, and copy and paste fallbacks.

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

```js
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

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

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`.

:::observed
Running `await navigator.clipboard.writeText('x')` from the DevTools Console in Chrome, with the Console rather than the page focused, rejects with `NotAllowedError: Document is not focused.`; the same call from a `click` handler in the page succeeds. A `readText()` call whose permission request was dismissed rejects with `NotAllowedError: Read permission denied.`, and `write()` with two items rejects with `NotAllowedError: Support for multiple ClipboardItems is not implemented.` All three strings are in Chromium's [`clipboard_promise.cc`](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/clipboard/clipboard_promise.cc) (chromium.googlesource.com).
:::

## 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

`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.

```js
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

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".

```js
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

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.

```js
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

- [Web Share API](/reference/capabilities/web-share/), where a clipboard write is the usual fallback
- [File System Access API](/reference/capabilities/file-system-access/), for files too large to round-trip through the clipboard
- [Clipboard API and events: write() method](https://www.w3.org/TR/clipboard-apis/#dom-clipboard-write) (w3.org)
- [Unblocking clipboard access](https://web.dev/articles/async-clipboard) (web.dev)
- [Async Clipboard API](https://webkit.org/blog/10855/async-clipboard-api/) (webkit.org)
- [Chrome Platform Status: Asynchronous Clipboard API](https://chromestatus.com/feature/5861289330999296) (chromestatus.com)