Javascript

Copy to Clipboard async using Clipboard API

Copy to Clipboard async (think from a Promise like fetch returns) using Clipboard API, working in Chrome, Safari and Firefox.

Wolfgang Rittner

updated June 30, 2024 · 3 min read

It's complicated, sort of

I wanted to fetch some text from the server and copy that to the Clipboard for my Single-File Ruby Templates app.
While Chrome doesn't really care what and when you copy into the User's Clipboard, Safari locks the Clipboard API down to only work when triggered by a direct user interaction. That sounds sensible, but makes it a bit tricky to use if you'd like copy something into the Clipboard from a Promise like the one fetch returns.
When I finally found out how to use Clipboard API in Safari even with async functions, I tested my solution in Firefox to make sure it worked there, too.
You'll never believe what happened next! It didn't.

After examining the error on the console, checking browser support on caniuse again, and reading through the documentation and details on browser support on mdn, I sighed:

Firefox has working support for the Clipboard API, but the ClipboardItem class and the navigator.clipboard.write function I used previously for making things work in Safari is behind the dom.events.asyncClipboard.clipboardItem preference on Firefox, which is turned off by default. You'd need to turn that on in about:config manually.
The only thing currently available in Firefox without changing preferences is navigator.clipboard.writeText. And that function only takes a string as an argument, so it can't be handed a Promise like navigator.clipboard.write, which was necessary to satisfy Safari's security constraints.

Checking for support

Sadly I was not able to find a solution to my initial problem - fetch something and copy the response into the clipboard - that worked across Chrome, Safari and Firefox. I ended up with introducing a conditional checking for ClipboardItem support. When satisfied, which will be the case on Chrome and Safari for now, we are going to use ClipboardItem and the navigator.clipboard.write function. For browsers like Firefox that do not support those APIs yet, it falls back to crossing fingers that the browser is not locked down to only support sync access to the clipboard and using writeText instead:

javascript
if (typeof ClipboardItem && navigator.clipboard.write) {
  // NOTE: Safari locks down the clipboard API to only work when triggered
  //   by a direct user interaction. You can't use it async in a promise.
  //   But! You can wrap the promise in a ClipboardItem, and give that to
  //   the clipboard API.
  //   Found this on https://developer.apple.com/forums/thread/691873
  const text = new ClipboardItem({
    "text/plain": fetch(someUrl)
      .then((response) => response.text())
      .then((text) => new Blob([text], { type: "text/plain" }))
  })
  navigator.clipboard.write([text])
} else {
  // NOTE: Firefox has support for ClipboardItem and navigator.clipboard.write,
  //   but those are behind `dom.events.asyncClipboard.clipboardItem` preference.
  //   Good news is that other than Safari, Firefox does not care about
  //   Clipboard API being used async in a Promise.
  fetch(someUrl)
    .then((response) => response.text())
    .then((text) => navigator.clipboard.writeText(text))
}

Hooray

At last, copying text fetched from a URL works across Chrome, Safari and Firefox. And all it took was an if statement like it's 2004.