JavaScript Clipboard Table

PieceWhat it doesField note
navigator.clipboard.writeText(s)Copy text asyncThe modern copy button - returns a promise, no execCommand hacks
readText()Paste text asyncNeeds read permission - browsers show the paste prompt
write(items)Copy rich contentClipboardItem with text/html AND text/plain - both, always
secure context onlyHTTPS requirementlocalhost counts; plain HTTP gets NO clipboard object at all
user gesture requiredClick-gatedCopy from a click/key handler - calls in load events reject
execCommand fallbackThe legacy pathDeprecated but the only option in insecure contexts - feature-detect
copy eventIntercept user copiese.clipboardData.setData - add attribution or strip formatting
paste eventRead pasted datae.clipboardData.getData('text/plain') - sanitize before inserting
Reference: the MDN Clipboard API reference. The clipboard is a user-agent-guarded resource: navigator.clipboard exists ONLY in secure contexts and only responds inside a user gesture - a copy button's click handler, never a load event. Bottom line: writeText is the one-liner every tool site needs, readText prompts for permission (the paste gate), rich copies should write BOTH text/html and text/plain flavors, and the copy/paste EVENTS are how you decorate or sanitize what users move themselves. Related tools: event listeners table (the gesture that unlocks the call), workers table (why clipboard never crosses threads), and security headers table (the HTTPS the API demands).

The clipboard is a user-agent-guarded resource: navigator.clipboard exists ONLY in secure contexts (HTTPS - localhost counts) and only responds inside a user gesture - a copy button's click handler works, the same call in a load event rejects. The guards are the API's whole shape.

Bottom line: navigator.clipboard.writeText(text) is the one-liner every tool site needs - promise-based, no hidden textarea hacks. Reading is stricter: readText shows a permission prompt, because pasted data flows the other way. And the copy/paste EVENTS are the two-way door for what users move themselves.

The honest part: the execCommand('copy') fallback era is over in secure contexts but still matters on plain HTTP intranets - feature-detect (if (navigator.clipboard)) and keep the legacy path behind the check. The API returning a rejected promise is the normal failure mode, not an exception to crash on.

How to use

  1. Build the copy button: button.addEventListener('click', () => navigator.clipboard.writeText(code).then(showCopied)) - gesture, promise, feedback.
  2. Copy rich content with both flavors: new ClipboardItem({ 'text/html': htmlBlob, 'text/plain': textBlob }) - paste into a rich editor gets formatting, into a terminal gets text.
  3. Sanitize pastes: element.addEventListener('paste', e => e.preventDefault(); insert(e.clipboardData.getData('text/plain'))) - reading plain text only strips hostile markup.

Frequently asked questions

Why does navigator.clipboard work on click but not on page load?

The user-gesture requirement. Browsers treat clipboard writes as a consent act - programmatic copies without a click, keypress or touch are rejected, because background page copy is exactly what hijacking scripts would do. The practical envelope: call writeText synchronously inside the event handler (or very shortly after - some browsers allow a short async window). This is also why 'copy on hover' designs fail and why copied feedback belongs in the same handler: gesture, action, confirmation is the whole interaction contract.

What does the readText permission prompt mean for paste UX?

Reading is the trust-sensitive direction: writeText gives the BROWSER's data out, readText pulls user clipboard data in - which could be a copied password. So browsers gate it behind a permission (and often an explicit paste permission prompt), and some only grant it while the document is focused. Design accordingly: paste buttons should expect a denial path (offer a manual textarea fallback), and never build flows where silent clipboard reading is required - the platforms will not allow it, and the prompts make it visible anyway.

Why write both text/html and text/plain flavors when copying rich content?

Because the paste target picks its flavor. A rich text editor consumes text/html (headings, links, bold survive); a code editor, terminal or plain input reads text/plain (clean characters, no markup debris). ClipboardItem accepts both under one copy, and the receiving app selects. Shipping only HTML means pasting into plain contexts produces raw tags; shipping only text means the rich editor gets an undifferentiated blob. The rule: always provide plain as the fallback flavor - it is the one every target can read.

How do the copy and paste events differ from the API methods?

They intercept the USER's own clipboard actions rather than initiating new ones. The copy event fires when a user copies YOUR selection - e.clipboardData.setData() there can add attribution, strip formatting, or replace the copied range before it lands. The paste event fires when the user pastes INTO your editable - e.clipboardData.getData('text/plain') reads what arrived so you can sanitize before insertion (the standard defense against pasted rich-text XSS). The API methods are programmatic; the events are hooks on human actions - and sanitizing pasted content is the one non-optional hook.

Related tools