← Blog

Clipboard API vs Paste Event: Which Should You Use?

Web browsers give you two ways to read what the user has copied: the traditional paste event and the modern asynchronous Clipboard API exposed through navigator.clipboard. They sound like two paths to the same destination — and for simple cases, they are — but under the hood they behave differently enough that picking the wrong one guarantees bugs in production. This article lays out the trade-offs, with a clear recommendation for each common scenario.

The paste event

The paste event is the older, synchronous approach. When the user presses Ctrl+V (or invokes Paste from a context menu), the browser fires a paste event on the target element. Your handler receives a ClipboardEvent whose clipboardData property exposes all available MIME types and their values, along with any files that came along.

document.addEventListener('paste', (e) => {
  const html = e.clipboardData.getData('text/html');
  const text = e.clipboardData.getData('text/plain');
  e.preventDefault(); // stop the default insert
  insertIntoEditor(html || text);
});

The key property: the event fires in the same tick as the user's keystroke, so you are inside a "user-gesture" window. Browsers permit quite a lot inside that window that they would refuse later — including reading the clipboard without any permission prompt.

The async Clipboard API

The async API is accessed through navigator.clipboard. It has three main methods:

  • navigator.clipboard.readText() — returns a promise for the plain-text value.
  • navigator.clipboard.read() — returns a promise for an array of ClipboardItem objects covering every MIME type.
  • navigator.clipboard.writeText(str) / navigator.clipboard.write(items) — write to the clipboard.

Reads require the page to have focus and — depending on the browser — may require a user-initiated gesture inside the last few seconds or an explicit clipboard-read permission grant. In exchange, you can read the clipboard at any time, not only inside a paste event.

Differences that matter

1. When the code runs

The paste event runs only when the user pastes. The async API runs whenever your code decides to call it — a button click, a timer, a WebSocket message. If your feature is "when the user clicks Import from Clipboard, read whatever is there", the async API is the only workable path. If the feature is "when the user pastes into this text field, transform the content", the paste event is a much better fit because you do not have to ask permission or chase focus.

2. Custom MIME types

The paste event in most browsers strips non-standard application/* MIME types for security reasons. If you need to read the proprietary data that one of your own web apps put on the clipboard earlier, only the async API (via navigator.clipboard.read()) reliably exposes it. This matters for any app that wants lossless copy-paste between two of its own tabs.

3. Images and files

Both APIs expose images, but the shape differs. In the paste event, images come as File objects in event.clipboardData.files. In the async API, they come as Blobs retrieved through clipboardItem.getType(type). Files (not images) copied from the OS are only reliably accessible through the paste event — the async API exposes them inconsistently across browsers.

4. Permission and focus

The paste event runs with no permission prompt because it is tied to a user gesture. The async API's read methods may fail if the page lacks focus or if the browser's permission policy requires an explicit grant. Chrome and Edge use the clipboard-read permission (which can be prompted, denied, or granted automatically via site installability); Safari requires a user gesture within a short time window; Firefox gates the API more aggressively.

5. Security and cross-origin

The async API's permission gating is stricter precisely because it can run without the user explicitly pasting. A malicious page could otherwise poll the clipboard for bank account numbers. The paste event carries less risk because the user has literally pressed Paste — a clear signal of intent to hand data to the page.

6. Synchronous versus asynchronous logic

Inside a paste handler you can call event.preventDefault() to cancel the browser's default insertion, then do your custom insertion synchronously. The async API does not integrate with paste at all — there is no event to cancel. If your feature needs to intercept a paste and replace it with a custom import, you must use the paste event.

A decision guide

Use the paste event when

  • You are building a rich-text editor or any input that needs to intercept Ctrl+V.
  • You need to handle image or file pastes consistently across browsers.
  • You need to call preventDefault() to cancel the browser's default behaviour.
  • You want zero permission prompts in exchange for only reading when the user pastes.

Use the async Clipboard API when

  • You are building an import-from-clipboard button that reads on demand.
  • Your app needs to write back to the clipboard (paste handlers cannot do this).
  • You need to read a custom application/* MIME type put there by another page.
  • You need to work outside a paste gesture, for example when polling for a clipboard change.

Use both

Many mature apps expose both paths. Rich-text editors use the paste event to intercept user pastes, and an "Import from clipboard" toolbar button to read on demand. A clipboard manager extension might use neither — browser extensions have a separate permission model — but a browser-based tool usually ends up wiring up the paste event and the async API side by side.

Common pitfalls

  • Assuming the paste event fires on all elements. It fires on the deepest target that is focused. If your custom canvas-based editor never sets focus, the paste event never arrives. Use tabindex="0" and call .focus() programmatically to enable pasting into non-input elements.
  • Catching permission errors silently. navigator.clipboard.read() can reject with NotAllowedError, SecurityError, or DataError. Each means a different UX state; surface them distinctly.
  • Writing with document.execCommand('copy'). This legacy API still works but is deprecated. For new code, use navigator.clipboard.writeText() — it is simpler, returns a real promise, and respects the modern security model.
  • Forgetting that Safari and Firefox differ. The async Clipboard API is available in all modern browsers, but the gating around navigator.clipboard.read() varies. Test every path on every browser you support.

A minimal hybrid pattern

The pattern below reads the clipboard through the async API if possible and falls back to prompting the user to paste if the async call is refused:

async function importFromClipboard() {
  try {
    const items = await navigator.clipboard.read();
    return await parseClipboardItems(items);
  } catch (err) {
    if (err.name === 'NotAllowedError') showPastePrompt();
    else throw err;
  }
}

The showPastePrompt() function opens a focused text field and wires a one-shot paste handler that processes the first incoming paste and closes itself.

Verifying end-to-end

Whichever API you pick, the ground truth for what the browser has access to is what Clipboard Inspector shows. If you wire up a paste handler and see fewer MIME types than the inspector reports, a parent handler is intercepting. If the async API returns an empty list, the browser is blocking the read. Seeing the difference between the two live, side by side, is the fastest way to understand what your users actually experience.