How to use the Clipboard API to Cut, Copy, and Paste in JavaScript

How to use the Clipboard API to Cut, Copy, and Paste in JavaScript

Until 2020, implementing cross-browser cut, copy, and paste options in web apps required convoluted synchronous document.execCommand() code which rarely worked as expected. This is now deprecated in favor of a new asynchronous Clipboard API. Support is generally good although not all browsers support the full specification.

Why do Apps Need Programmatic Clipboard Access?

Those with a reasonable level of IT literacy often use keyboard shortcuts to cut, copy, and paste:

  • Ctrl / Cmd + C to copy
  • Ctrl / Cmd + X to cut
  • Ctrl / Cmd + V to paste
  • Ctrl / Cmd + Shift + V to paste without formatting
  • perhaps similar options to access clipboard history managers
  • long-press on smart phones and tablets.

This raises several issues:

  • These shortcuts are not necessarily obvious to those with limited computing experience.

  • Selecting the correct text can be difficult -- we've all accidentally highlighted unnecessary whitespace when copying passwords and access codes.

  • How do you copy non-textual content such as images or audio data? How do you copy SVG images generated using text code?!

The Clipboard API allows you to:

  1. offer UI controls such as copy and paste buttons
  2. handle text and binary formats
  3. modify content when the user activates clipboard actions.

Clipboard Crisis

Browser vendors had to consider security concerns when implementing the API. Your clipboard could contain personal or secure data such as confidental documents or passwords. Therefore, the Clipboard API is only available when the web page:

  1. uses HTTPS (or a localhost address during development)
  2. is in an active browser tab and not in the background, and
  3. the functionality is triggered by a user interaction such as a click.

If your page is running inside an <iframe>, the parent page must set explicit clipboard permissions:

<iframe
  src="https://site.com/child.html"
  allow="clipboard-read; clipboard-write"
></iframe>

Finally, the user may see a permissions prompt in some browsers -- typically the first time they use paste functionality:

approve clipboard use

The Permissions API allows you to check the clipboard-read and clipboard-write states and force a prompt. Be aware these are non-standard and only available in Chromium-based browsers (Chrome, Edge, Opera, Brave, and Vivaldi).

Detect Clipboard API Support

You can detect for Clipboard API support using navigator.clipboard:

if (navigator.clipboard) {
  console.log('Clipboard API available');
}

This confirms that some API features are available. It will be necessary to make further checks for specific methods when attempting to manipulate the clipboard, e.g.

if (navigator.clipboard.writeText) {
  console.log('Can copy text to clipboard');
}

Write Text to the Clipboard

To write any text to the clipboard, use the Promise-based writeText() method:

document.getElementById('copybutton').addEventListener('click', async () => {

  try {
    await navigator.clipboard.writeText('added to clipboard');
  }
  catch (err) {
    console.error('Could not write to clipboard', err);
  }

});

This code writes text to the clipboard when an element with the ID copybutton is clicked. The browser checks for recent user interaction so it's generally best to run the writeText() method within an event handler.

You can write any string to the clipboard but, if necessary, you can get whatever text the user has selected on the page and manipulate it as necessary:

const selectedText = window.getSelection().toString().trim();

if (selectedText) {
  await navigator.clipboard.writeText(
    selectedText + '\nCopied from site.com';
  );
}

The writeText() method has full support in most modern browsers.

Read Text from the Clipboard

To read text content from the clipboard, use the Promise-based readText() method:

document.getElementById('pastebutton').addEventListener('click', async () => {

  let text = '';

  try {
    text = await navigator.clipboard.readText();
  }
  catch (err) {
    console.error('Could not read from clipboard', err);
  }

  if (text) {
    // use text accordingly
  }

}

This code reads text from the clipboard when an element with the ID pastebutton is clicked. The browser checks for recent user interaction so it's generally best to execute readText() within an event handler.

The readText() method has full support in most modern browsers.

Write Any Data to the Clipboard

The write() method supports all data types including binary and text. However, browser support is more limited and the code is not as simple.

An array of ClipboardItem objects is passed to the write() method. These objects are constructed by passing an object with the MIME type as the key and a raw data Blob as the value. The following code creates a ClipboardItem object from a text string:

const
  type = 'text/plain',
  blob1 = new Blob(['text to write to clipboard'], { type }),
  item1 = new ClipboardItem({ [type]: blob });

The following example creates a ClipboardItem object from an image loaded using the Fetch API:

const
  img   = await fetch('image.png'),
  blob2 = await img.blob(),
  item2 = new ClipboardItem({ [blob.type]: blob };

The following example creates a ClipboardItem object from an image rendered in a canvas element with the id mycanvas:

const
  canv  = document.getElementById(`mycanvas`),
  blob3 = await canv.toBlob(),
  item3 = new ClipboardItem({ [blob.type]: blob };

You can now write the resulting item object(s) to a single clipboard entry:

try {
  await navigator.clipboard.write([ item1, item2, item3 ]);
}
catch (err) {
  console.error('Could not write to clipboard', err);
}

Remember the browser checks for a recent user interaction so this code should normally be run within an event handler.

Read Any Data from the Clipboard

The read() method supports all data types including binary and text. Again, browser support is more limited. It returns an array of ClipboardItem objects which you can process as required:

try {

  const clipboard = await navigator.clipboard.read();

  for (item of clipboard) {

    for (type of item.types) {

      // get blob data for item
      const blob = await item.getType(type);

      let newElement;

      // text item
      if (type === 'text/plain') {
        newElement = document.createElement('p');
        newElement.textContent = await blob.text();
      }

      // image item
      if (type.startsWith('image')) {
        newElement = new Image(),
        newElement.src = URL.createObjectURL(blob);
      }

      // append text or image to document
      if (newElement) {
        document.body.appendChild(newElement);
      }

    }

  }

}
catch (err) {
  console.error('Could not read from clipboard', err);
}

Remember the browser checks for a recent user interaction so this code should normally be run within an event handler.

Clipboard Events

cut, copy, and paste events are triggered when the user interacts with the clipboard in any way (keyboard shortcut, long tap, right-click menu, read() call, write() call, etc.) Handler code can intercept these events and send their own the ClipboardItem. Alternatively, the event object passed to the handler also provides a clipboardData property which can be examined or modified as necessary.

For example, you cannot normally cut text from a web page but the following event handler converts a selection to uppercase text, writes it to the clipboard, and deletes it from the document:

body.addEventListener('cut', e => {

  // selection available>
  const selection = document.getSelection();
  if (selection.type !== 'Range') return;

  // write uppercase text to clipboard
  e.clipboardData.setData(
    'text/plain',
    selection.toString().toUpperCase()
  );

  // remove selection from document
  selection.deleteFromDocument();

  // stop default cut/copy
  e.preventDefault();

});

The following example intercepts a paste into a <textarea> field with the id of mytext and forces it to uppercase:

document.getElementById('mytext').addEventListener('paste', e => {

  // modify pasted text
  e.target.value = e.clipboardData.getData('text').toUpperCase();

  // stop default paste
  e.preventDefault();

});

Conclusion

The Clipboard API is easy to use and has good support in all modern browsers. That said, there are plenty of edge cases to catch you out and even organizations with plenty of resources don't always get it right (try copying a image from a Google Docs document to your local OS!)

I suggest you use the Clipboard API as a functional enhancement when it makes practical sense. For example, an app which allows you to copy SVG image code (XML) could show it in a <textarea> or a <code> block with CSS user-select: all set. Copy and paste is then possible in all browsers including ancient applications. In browsers where navigator.clipboard.writeText is supported, that element can be hidden and replaced with a single button which copies the SVG code to the clipboard.

Finally, remember to test your clipboard code across a range of desktop and mobile browsers with mouse, keyboard, and touch control.

Did you find this article valuable?

Support Craig Buckler by becoming a sponsor. Any amount is appreciated!