Clipboard API
Reading from and writing to the system clipboard — copy, cut, paste, rich content, and the legacy document.execCommand fallback.
navigator.clipboard only exists in a secure context: HTTPS, or localhost during development. Open the same page over plain HTTP, such as a staging server or your laptop's LAN address on a phone, and navigator.clipboard is undefined. The copy button throws a TypeError, nothing is copied, and the user gets no feedback at all.
const copyBtn = document.querySelector("#copy-btn");
const codeEl = document.querySelector("pre");
copyBtn.addEventListener("click", async () => {
await navigator.clipboard.writeText(codeEl.textContent);
copyBtn.textContent = "✓ Copied!";
});
// page opened over plain HTTP, e.g. http://staging.test (not localhost):
console.log(window.isSecureContext, typeof navigator.clipboard);
// false "undefined"
// click -> TypeError: Cannot read properties of undefined (reading 'writeText')Copy and paste from JavaScript
Copy text to clipboard
// The simplest pattern — copy any text:
async function copyText(text) {
try {
await navigator.clipboard.writeText(text);
console.log("Copied!");
} catch (error) {
console.error("Copy failed:", error);
}
}
// A copy button that gives feedback:
const copyBtn = document.querySelector("#copy-btn");
const codeEl = document.querySelector("pre");
copyBtn.addEventListener("click", async () => {
await navigator.clipboard.writeText(codeEl.textContent);
copyBtn.textContent = "✓ Copied!";
setTimeout(() => { copyBtn.textContent = "Copy"; }, 2000);
});The copyText() helper above at least catches the error, but the button version doesn't. Check window.isSecureContext first, fall back to the legacy execCommand("copy") technique (covered in the Intermediate tab) on plain HTTP, and always tell the user what happened:
const copyBtn = document.querySelector("#copy-btn");
const codeEl = document.querySelector("pre");
async function copy(text) {
if (window.isSecureContext && navigator.clipboard) {
await navigator.clipboard.writeText(text); // HTTPS or localhost
return true;
}
return legacyCopy(text); // plain HTTP: fall back
}
function legacyCopy(text) {
const el = document.createElement("textarea");
el.value = text;
el.style.position = "absolute";
el.style.left = "-9999px";
document.body.appendChild(el);
el.select();
const ok = document.execCommand("copy");
document.body.removeChild(el);
return ok;
}
copyBtn.addEventListener("click", async () => {
try {
const ok = await copy(codeEl.textContent);
copyBtn.textContent = ok ? "✓ Copied!" : "Press Ctrl+C to copy";
} catch {
copyBtn.textContent = "Press Ctrl+C to copy"; // never leave the user guessing
}
console.log(copyBtn.textContent);
});
// "✓ Copied!" (on http://staging.test and on localhost)Reading the clipboard
// Reading requires user permission (browser asks):
async function pasteText() {
try {
const text = await navigator.clipboard.readText();
input.value = text; // paste into an input
} catch (error) {
// User denied or clipboard access not available
console.error(error.message);
}
}
// Listen for paste events (no permission needed — user initiates):
document.addEventListener("paste", (event) => {
const text = event.clipboardData.getData("text/plain");
console.log("User pasted:", text);
event.preventDefault(); // prevent default paste behaviour
});Official Sources
The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.