Skip to content

Illustration by unDraw.

Copy text and images to clipboard

How to easily copy text and images to the clipboard using the asynchronous Clipboard API.

In this article, we will look at a simple way of copying text in the clipboard and the not-so-consistent way of copying images to a user's clipboard, by using the relatively new Async Clipboard API.

Warning: document.execCommand was widely used in the past for copying text to a user's clipboard but its usage is discouraged and it may cease to work at any time!

Permissions#

If you are only writing to a user's clipboard you don't need to worry about browser permissions.

However, there are two things you need to keep in mind:

  1. Your site should be served over HTTPS
  2. Clipboard access is only granted when the page is the active browser tab

Reading from the clipboard always requires permission and we will be looking more into it in another article.

Copy text to clipboard#

Copying text to the clipboard is rather quick and easy:

await navigator.clipboard.writeText("text to copy!");

That's it! Really!

For the sake of web and browser support, you can catch any errors you might get from older browsers and call document.execCommand instead as a fallback. Please be mindful that document.execCommand is discouraged and might stop working at any point in time!

export async function copyToClipboard (text) {
  try {
    // try to use Clipboard API
    await navigator.clipboard.writeText(text);
    return true
  } catch (_) {
    // Clipboard API is not supported
    const el = document.createElement('textarea')
    el.value = text
    document.body.appendChild(el)
    el.select()
    const result = document.execCommand('copy')
    document.body.removeChild(el)
    return result === 'unsuccessful' ? false : true
  }
}
Note that if you are using this as an event listener in Gatsby you might need to add a window check statement at the beginning of this function:
if (typeof window === 'undefined') return false

Copy image to clipboard#

For copying an image to the clipboard, it would look a bit different than copying text like above, for the reason that we need to convert our image to a Blob object before writing it to the clipboard.

Essentially we need to create a Blob object by firstly fetching it from the network:

async function getImageBlobFromUrl(url) {
  const fetchedImageData = await fetch(url)
  const blob = await fetchedImageData.blob()
  return blob
}
Warning: Creating a Blob object from a <canvas> element is not recommended! If you are still interested though have a look at this StackOverflow answer and make sure you understand how it works and what will be the issues while using it.

Copy image blob to clipboard (Chrome)#

After generating the image blob by using any of the methods mentioned above, we can then copy the blob into the clipboard like below:

document.querySelector('.copy-button').addEventListener('click', async () => {
  const src = document.querySelector('.image-to-copy').src
  try {
    const blob = await getImageBlobFromUrl(src)
    await navigator.clipboard.write([
      new ClipboardItem({
        [blob.type]: blob
      })
    ])
    alert('Image copied to clipboard!')
  } catch (err) {
    console.error(err.name, err.message);
    alert('There was an error while copying image to clipboard :/')
  }
})

There are several issues with the above implementation though if you ask me, which brings us to the limitations of the Clipboard API.

Limitations#

Chrome vs Safari#

The first issue is that, you need to have different logic for Safari because while Chromium browsers require a Blob type when you create a new ClipboardItem e.g. {'<IMAGE MIME TYPE>': Blob}, Safari on the other hand requires an unresolved Promise that returns a Blob object e.g. {'<IMAGE MIME TYPE>': Promise<Blob>} 🤷‍♂️ . Otherwise, you would get an error like the one below:

NotAllowedError – "The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission."

Then because of the above, there is no way of knowing what mime-type the image is if you don't create its Blob in the first place. So for the Safari implementation, we can get the image extension from its URL and use it to create its mime-type like below:

function getImageMimeTypeFromUrl(url) {
  return `image/${url.match(/([a-z]+)$/)[0]}`
}
Tip: Check out this gist for a utility function on detecting Safari browsers with JavaScript.

And the final implementation with the unresolved Promise will look like this:

if (isSafari()) {
  const imageMimeType = getImageMimeTypeFromUrl(src)
  await navigator.clipboard.write([
    new ClipboardItem({
      [imageMimeType]: getImageBlobFromUrl(src)
    })
  ])  
}

However, to all browsers' defense you wouldn't have to support a long list of mime-types because as we will see in a bit, only a couple are supported 😕

Supported mime-types#

Currently only 'text/plain' and 'image/png' are implemented in both Chrome and Safari. I'm hoping that the list will get larger but for now only these two made the cut from what it seems.

Luckily, there is a workaround for SVGs, as they can be copied as 'text/plain' instead of 'image/svg+xml'. So a Chrome implementation would look something like this:

async function copyImageToClipboard(img) {
  const src = img.src
  const imageMimeType = getImageMimeTypeFromUrl(src)
  const blob = imageMimeType === 'image/svg' 
    ? await getTextBlobFromUrl(src) 
    : await getImageBlobFromUrl(src)
  
  await navigator.clipboard.write([
    new ClipboardItem({
      [blob.type]: blob
    })
  ])
}

async function getTextBlobFromUrl(url) {
  const response = await fetch(url);
  const source = await response.text();
  return new Blob([source], { type: 'text/plain' });
};

copyImageToClipboard(document.querySelector('.image-to-copy'))
Tip: For more information on multi-mime-type copying have a look at this article by Thomas Steiner.

Browser support#

Finally, the browser support despite the limited options outlined above doesn't look that bad:

Live demo and code#

For a live demo visit this website and find the code on Glitch.

Further reading#