JavaScript clipboard access

Accessing the clipboard with old document.execCommand had been superseded by the new Clipboard API. The latter API provides the ability to read from and write to the system clipboard asynchronously. But it is a relatively recent addition to the web standards and may not be fully supported in all browsers, check the browser compatibility before using.

Unlimited clipboard access can be dangerous. Especially reading from it may stole the user’s sensitive data like password. Writing may also bring malicious content to the clipboard that you may paste it somewhere unexpectedly. Therefore there are some restrictions for the methods used to access the clipboard, and reading is more strict.

At the end of the article, we give an example to write an given text to the clipboard. The example tries to work on most browsers.

Deprecated document.execCommand() for clipboard access

Note the document.execCommand() method is no longer recommended and may not be supported in some browsers. But it is still useful in some cases, such as on browsers that have not implemented the Async Clipboard API or not fully implemented.

The document.execCommand() can be used to execute commands like cut, copy and paste to interact with the clipboard. Its effect is analogous to that of a user executing such actions by pressing the keyboard.

Access clipboard with document.execCommand()

An example to execute paste ,copy and cut commands.

HTML

<textarea id="text-a">A</textarea>
<textarea id="text-b">B</textarea>

<button id="copy-button">Copy</button>
<button id="cut-button">Cut</button>
<button id="paste-button">Paste</button>

JavaScript

// Copy content of fromElem or the current selection to the clipboard.
function copy(fromElem = null) {
    if (fromElem) {
        fromElem.select(); // Make its content selected.
    }
    document.execCommand("copy");
}

// Remove content of fromElem or the current selectionand 
// and copy it to the clipboard. 
function cut(fromElem = null) {
    if (fromElem) {
        fromElem.select(); // Make its content selected.
    }
    document.execCommand("cut");
}

// Paste content from the clipboard to the "toElem" elment.
function paste(toElem) {
    toElem.focus(); // Make it focused.
    // Or use document.execCommand("paste", false, "Some given text");
    document.execCommand("paste");
}

const textA = document.querySelector("#text-a");
const textB = document.querySelector("#text-b");
document.querySelector("#copy-button")
    .addEventListener("click", () => copy(textA));
document.querySelector("#cut-button")
    .addEventListener("click", () => cut(textA));
document.querySelector("#paste-button")
    .addEventListener("click", () => paste(textB));

In the above example, all document.execCommand() calls are inside user-generated event handlers. If "cut" or "copy" is called outside such a handler, it may not work. On Chrome (v104), it just do not work. On Firefox, you may see the following warning:

document.execCommand(‘cut’/‘copy’) was denied because it was not
 called from inside a short running user-generated event handler.

And for "paste", even inside a user-generated event handler, it may be blocked for security.

Other commands

document.execCommand() can also be used to execute commands that the Clipboard API does not have. Like the insertText command which enables you to insert the given plain text at the insertion point programmatically.

Issues with document.execCommand()

  • It is synchronous. If the operation takes long time, it blocks the page.
  • There seems not a clear permission specification for clipboard access with document.execCommand().

The Clipboard API

Besides in a asynchronous way, with the Clipboard API, you can write and read arbitrary data to and from the clipboard, like text, rich text, HTML, images, etc. And it is convenient to copy a given data instead of the current selection to the clipboard.

Access clipboard with the Async Clipboard API

An example to read text and write text from and to the clipboard

HTML

<button id="copy-async-button">Copy async</button>
<button id="paste-async-button">Paste async</button>

JavaScript

function copyAsync(text) {
    if (navigator.clipboard) {
        // It will fail if the codument is not focused: "DOMException: Document is not focused."
        // In this case, click the page to make it focused.
        navigator.clipboard.writeText(text)
            .then(
                () =&gt; console.log("Copy async successful:", text),
                (err) =&gt; console.error("Copy async failed:", err)
            );
    }
}

function pasteAsync() {
    if (navigator.clipboard) {
        // It may fail if the `clipboard-read` permission is denied
        // to the current contenxt.
        // Firefox (at least v99) does not support
        // navigator.clipboard.readText()
        // or navigator.clipboard.read().
        navigator.clipboard.readText()
            .then(
                text =&gt; console.log("Paste async successful:", text),
                err =&gt; console.error("Paste async failed:", err)
            );
    }
}

document.querySelector("#copy-async-button")
    .addEventListener("click", () =&gt; copyAsync("Some text"));
document.querySelector("#paste-async-button")
    .addEventListener("click", pasteAsync);

Note the effect of the example vary depending on the browser and the context (like http or https ). See more in the Browser compatibility section.

The Clipboard API works only when the document is focused, otherwise you will get an exception in the console:

DOMException: Document is not focused.

In this case, click the page to make the document focused.

Permissions

Writing to the clipboard with the Clipboard API needs clipboard-write permission which is defined in Permissions API. This permission is granted automatically to pages when they are in the active tab.

Reading from the clipboard with the Clipboard API needs clipboard-read permission which must be requested. On some browsers of new versions, if the corresponding permission is not granted when reading from or writing to the clipboard, the browsers automatically prompt the user to request the permission.

You can also query whether a permission has been granted to the current context before using the API (clipboard-write and clipboard-read permission names are not supported in Firefox, at least not for v99).

navigator.permissions.query({name: "clipboard-read"})
    .then(result => {
        // state can be one of "granted", "prompted", "denied"
        if (result.state === "granted") { /* ... */}
    });

If the clipboard-read is denied, you will see below exception in the console if you click the paste button in the example.

DOMException: Read permission denied.

Browser compatibility

Check the full Browser compatibility.

Firefox

In the above example, we check the availability of the Clipboard API with navigator.clipboard before using them.

However, that is not enough. Firefox (v99) supports navigator.clibpard.writeText() but not navigator.clibpard.readText(). You need to put the call inside a try/catch block to work with most browsers.

And the browser does not support permission names of clipboard-write and clipboard-read. You will get an exception like below when you query it with Permission API:

Uncaught (in promise) TypeError: 'clipboard-read' (value of 'name'
 member of PermissionDescriptor) is not a valid value for enumeration
 PermissionName.

Chrome

Chrome (v104) does not prompt the user for permission when a permission like clibpard-read is denied. It just fails with an exception.

Safari

Safari use the WebKit browser engine. WebKit supports four MIME type representations: "text/plain""text/html""text/uri-list", and "image/png".

The Clipboard API is not supported on Safari until Safari V13.1 (year of 2020) and Safari iOS V13.4. You need special process for navigator.clibboard.write() on Safari. Below is an example to copy image data to the clibboard.

An example to copy an image for Safari and other browsers

Clipboard.write() writes arbitrary datato the clipboard., such as images,

HTML

<button id="paste-image-async-button">Paste image async</button>

JavaScript:

function copyImageAsync() {
    fetch("a.png")
        .then(response =&gt; {
            if (!response.ok) { // Check whether it is ok
                throw new Error(`HTTP error! Status: ${response.status}`);
            }

            return response.blob();
        })
        .then(blob =&gt; {
            console.log('blob.type =', blob.type);

            // On Safari, each ClipboardItem is initialized with
            // a mapping of MIME type to Promise.
            if (navigator.userAgent.includes("Safari")) { // On safari,
                navigator.clipboard.write([
                    new ClipboardItem({
                        [blob.type]: Promise.resolve(blob)
                    })
                ]);

            } else {
                navigator.clipboard.write([
                    new ClipboardItem({
                        [blob.type]: blob
                    })
                ]);
            }
        }).catch(err =&gt; console.error(err));;
}
document.querySelector("#paste-image-async-button")
    .addEventListener("click", copyImageAsync);

Note special process for ClipboardItem on Safari

WebKit’s Async Clipboard API: Each ClipboardItem is initialized with a mapping of MIME type to Promise (see ).

See more at webkit: Async Clipboard API. It also specify how to use the new Async Clipboard API and old document.execCommand("copy") on Safari.

Conclusion

Now you have known the main differences between document.execCommand() and the Clipboard API. To be compatible with most browsers, we need to use both methods to work with the clipboard access.

Below example is a variant of example in webkit: Async Clipboard API. It writes a given text to the clipboard which tries to be compatible to as many browsers as possible.

HTML

<button id="copy-given-text-button">Copy given text</button>

JavaScript

// Copy the given text to the clipboard
function copyGivenText(text) {
    try {
        if (!navigator.clipboard) {
            // First create a textarea element to hold the given text
            // and set its content being selected.
            // A他 remove it after copping the selection to the clipboard.

            let textarea = document.createElement("textarea");
            textarea.style.opacity = "0";
            textarea.style.position = "fixed";
            textarea.value = text;
            document.body.appendChild(textarea);

            textarea.focus();
            textarea.setSelectionRange(0, textarea.value.length);
            document.execCommand("copy");
            console.log("Copy successful");

            textarea.remove();
        } else {
            navigator.clipboard.writeText(text)
                .then(
                    () => console.log("Copy successful"),
                    () => console.log("Copy failed")
                );
        }
    } catch (err) {
        console.error(err);
    }
}
document.querySelector("#copy-given-text-button")
    .addEventListener("click", () => copyGivenText("Given text"));

Reference

  • MDN: Clipboard API

    > Note: This API is not available in Web Workers (not exposed via WorkerNavigator).

  • MDN: Interact with the clipboard

    The Clipboard API is only available to Secure Contexts (An active tab in the browser is also considered to be a secure context).

    Using the Clipboard API in an browser extension requires the permission "clipboardRead" or "clipboardWrite" in the manifest.json file.

  • web.dev: Unblocking clipppbard acess

    To use the Clipboard API in iframes, you need to pass either or both clipboard-read or clipboard-write like:


  • WebKit’s Async Clipboard API

    > Note that both clipboard.write and clipboard.writeText are asynchronous. If you attempt to write to the clipboard while a prior clipboard writing invocation is still pending, the previous invocation will immediately reject, and the new content will be written to the clipboard.

    Sanitization

    Both "text/html" and "image/png" data is sanitized before writing to the pasteboard.

    Similar to writing data, reading data from the system clipboard involves sanitization to prevent users from unknowingly exposing sensitive information.

  • w3.org: Async Clipboard API