StackPractices
beginner By Mathias Paulenko

JavaScript Clipboard Copy and Paste

Copy text to clipboard programmatically in JavaScript with fallback

Topics: frontend

Overview

The Clipboard API lets you copy and paste text programmatically in the browser. navigator.clipboard.writeText() is the modern approach, with document.execCommand("copy") as a fallback for older browsers. This approach handles copying text, reading from clipboard, handling permissions, and paste event interception.

When to Use

  • For alternatives, see JavaScript Infinite Scroll Pagination with.

  • You need a “Copy to clipboard” button for code snippets, URLs, or tokens

  • You want to read clipboard content on paste events

  • You are building a rich text editor that intercepts paste

  • You need clipboard support across modern and legacy browsers

Solution

Basic copy to clipboard

async function copyToClipboard(text) {
    try {
        await navigator.clipboard.writeText(text);
        console.log("Copied to clipboard");
        return true;
    } catch (err) {
        console.error("Failed to copy:", err);
        return false;
    }
}

// Usage
document.getElementById("copy-btn").addEventListener("click", () => {
    copyToClipboard("https://example.com/share-link");
});

Copy with fallback for older browsers

async function copyToClipboard(text) {
    if (navigator.clipboard && window.isSecureContext) {
        try {
            await navigator.clipboard.writeText(text);
            return true;
        } catch (err) {
            // Fall through to fallback
        }
    }

    // Fallback: execCommand
    const textarea = document.createElement("textarea");
    textarea.value = text;
    textarea.style.position = "fixed";
    textarea.style.left = "-9999px";
    document.body.appendChild(textarea);
    textarea.focus();
    textarea.select();

    try {
        const success = document.execCommand("copy");
        document.body.removeChild(textarea);
        return success;
    } catch (err) {
        document.body.removeChild(textarea);
        return false;
    }
}

// Usage
const button = document.querySelector("#copy-btn");
button.addEventListener("click", async () => {
    const ok = await copyToClipboard("text to copy");
    if (ok) {
        button.textContent = "Copied!";
        setTimeout(() => button.textContent = "Copy", 2000);
    }
});

Read from clipboard

async function readClipboard() {
    try {
        const text = await navigator.clipboard.readText();
        console.log("Clipboard content:", text);
        return text;
    } catch (err) {
        console.error("Failed to read clipboard:", err);
        return null;
    }
}

// Usage: read clipboard on button click (requires user gesture)
document.getElementById("paste-btn").addEventListener("click", async () => {
    const text = await readClipboard();
    if (text) {
        document.getElementById("output").value = text;
    }
});

Intercept paste events

document.getElementById("editor").addEventListener("paste", (event) => {
    event.preventDefault();

    const clipboardData = event.clipboardData || window.clipboardData;
    const pastedText = clipboardData.getData("text/plain");

    // Sanitize or transform pasted content
    const cleanText = pastedText
        .replace(/<[^>]*>/g, "")
        .replace(/\s+/g, " ")
        .trim();

    document.execCommand("insertText", false, cleanText);
});

// Handle image paste
document.getElementById("image-editor").addEventListener("paste", (event) => {
    const items = event.clipboardData?.items;
    if (!items) return;

    for (const item of items) {
        if (item.type.startsWith("image/")) {
            const file = item.getAsFile();
            const reader = new FileReader();

            reader.onload = (e) => {
                const img = document.createElement("img");
                img.src = e.target.result;
                document.getElementById("preview").appendChild(img);
            };

            reader.readAsDataURL(file);
        }
    }
});

Copy button for code blocks

function addCopyButtons() {
    const codeBlocks = document.querySelectorAll("pre code");

    codeBlocks.forEach((codeBlock) => {
        const button = document.createElement("button");
        button.className = "copy-btn";
        button.textContent = "Copy";
        button.style.position = "absolute";
        button.style.top = "8px";
        button.style.right = "8px";

        button.addEventListener("click", async () => {
            const text = codeBlock.textContent;
            const ok = await copyToClipboard(text);

            if (ok) {
                button.textContent = "Copied!";
                setTimeout(() => button.textContent = "Copy", 2000);
            }
        });

        const pre = codeBlock.parentElement;
        pre.style.position = "relative";
        pre.appendChild(button);
    });
}

addCopyButtons();

Check clipboard permissions

async function checkClipboardPermission() {
    try {
        const permission = await navigator.permissions.query({
            name: "clipboard-write"
        });

        if (permission.state === "granted") {
            return true;
        }

        if (permission.state === "prompt") {
            // Will ask on next clipboard operation
            return null;
        }

        return false;
    } catch (err) {
        // permissions API not supported, assume available
        return true;
    }
}

// Usage
const canCopy = await checkClipboardPermission();
if (canCopy === false) {
    showNotification("Clipboard access denied. Please enable it in browser settings.");
}

Explanation

The Clipboard API has two main methods:

  • navigator.clipboard.writeText(text): Writes text to the clipboard. Returns a promise. Requires a secure context (HTTPS or localhost) and must be triggered by a user gesture (click, keypress).
  • navigator.clipboard.readText(): Reads text from the clipboard. Returns a promise. Requires the clipboard-read permission, which browsers typically prompt for on first use.

The fallback document.execCommand("copy") is deprecated but still works in older browsers. It requires a visible or off-screen textarea element that is selected before calling execCommand.

Key constraints:

  • Secure context: navigator. clipboard only works on HTTPS or localhost. On HTTP, use the fallback.
  • User gesture: Clipboard operations must be triggered by a user action (click, tap, keypress). You cannot copy programmatically without user interaction.
  • Permissions: Reading clipboard requires explicit permission. Writing is usually allowed with a user gesture.
  • Paste events: The paste event fires on focusable elements (inputs, textareas, contenteditable). clipboardDatato access pasted content andevent. preventDefault()` to override default behavior.

Variants

MethodBrowser SupportAsyncUse When
navigator.clipboard.writeTextModern browsersYesDefault choice, HTTPS
document.execCommand("copy")All browsersNoFallback for legacy
navigator.clipboard.readTextModern browsersYesReading clipboard
Paste event handlerAll browsersNoIntercepting paste

Guidelines

  • Always use navigator.clipboard first, with execCommand as fallback.
  • Require HTTPS for the modern API. It does not work on HTTP.
  • Provide visual feedback after copy (e.g., “Copied!” text or icon change).
  • Handle errors gracefully. Clipboard access can be denied by the user.
  • Sanitize pasted content in rich text editors to prevent XSS.
  • Do not read clipboard without user interaction. Browsers block this.
  • Test on mobile browsers. Some have different clipboard permission flows.

Common Mistakes

  • Calling navigator.clipboard on HTTP. It only works on HTTPS or localhost.
  • Not providing a fallback for older browsers. The modern API is not universal.
  • Not handling the rejected promise. The user may deny clipboard permission.
  • Forgetting to remove the off-screen textarea in the fallback. It causes memory leaks.
  • Trying to read clipboard without a user gesture. Browsers block this for security.
  • Not sanitizing pasted HTML. Pasting from Word or browsers can inject malicious markup.

Troubleshooting

  • Component does not re-render: verify state reference, props, and memoization. A mutated object can bypass change detection.
  • Style does not apply in production: check that CSS is loaded, class names are not mangled, and specificity wins. Purge unused styles carefully.
  • Build fails after dependency update: read the changelog, pin versions, and clean the lock file.
  • Accessibility audit fails: add labels, landmarks, focus management, and color contrast.
  • Hydration mismatch: ensure server and client render the same initial HTML. random, or window during SSR.

Key Takeaways

  • Apply javascript clipboard copy and paste when you need a practical solution for your use case.
  • Monitor performance after implementation; measure latency, errors, and resource usage before and after.
  • Check the Troubleshooting section for common failures; most have documented root causes with fixes.
  • Keep dependencies updated and run tests in CI to prevent production regressions.

Common Production Pitfalls

  • Copying the example without adapting it to real data volumes and failure modes.
  • Skipping load and error-injection tests before the first production deployment.
  • Hard-coding values that should be configurable per environment.
  • Forgetting to add logging and monitoring at each step.
  • Deploying without a rollback plan or a tested backup strategy.
  • Assuming the minimal example will scale without adding caching or batching.
  • Not documenting the version and configuration used in production.
  • Letting the recipe sit unchanged when dependencies or scale evolve.

Frequently Asked Questions

Why does navigator.clipboard not work on localhost with HTTP?

The Clipboard API requires a secure context. localhost is considered secure even over HTTP, but other HTTP origins are not. Use HTTPS in production or test on localhost.

How do I copy rich text or HTML to the clipboard?

Use navigator.clipboard.write() with a ClipboardItem:

const htmlBlob = new Blob(["<b>Bold text</b>"], { type: "text/html" });
const textBlob = new Blob(["Bold text"], { type: "text/plain" });

await navigator.clipboard.write([
    new ClipboardItem({
        "text/html": htmlBlob,
        "text/plain": textBlob
    })
]);
Can I copy without a button click?

No. Browsers require a user gesture (click, tap, keypress) for clipboard operations. You cannot copy automatically on page load or in a timeout without prior user interaction.

How do I copy an image to the clipboard?

Use navigator.clipboard.write() with a PNG blob:

const response = await fetch("image.png");
const blob = await response.blob();

await navigator.clipboard.write([
    new ClipboardItem({ "image/png": blob })
]);