clipboard, clipboard-polyfill, clipboardy, and copy-paste are npm packages that provide programmatic access to the system clipboard in JavaScript environments. They address the challenge of copying text (and sometimes other data) to the user's clipboard across different browsers and execution contexts — from browser-based frontend apps to Node.js scripts. Each package targets distinct runtime environments and use cases: some focus exclusively on modern browser APIs with graceful fallbacks, others support both browser and Node.js, while one is strictly for Node.js command-line or server-side usage. Understanding their scope, API design, and environment compatibility is essential for making the right architectural choice.
Copying and pasting data programmatically seems simple, but browser security restrictions, API inconsistencies, and environment differences make it surprisingly tricky. The four packages under review each tackle this problem differently — some for browsers, some for Node.js, and one that’s past its prime. Let’s cut through the noise and see which tool fits your real-world scenario.
This is the first and most critical filter. Not all clipboard packages work everywhere.
clipboard and clipboard-polyfill are browser-only. They rely on DOM APIs like document.execCommand() or navigator.clipboard and will throw errors if used in Node.js.
clipboardy is Node.js-only. It uses platform-specific commands (pbcopy/pbpaste on macOS, clip/powershell on Windows, xclip/xsel on Linux) via child processes. It cannot run in a browser.
copy-paste was also designed for Node.js, but it’s deprecated — more on that later.
💡 Rule of thumb: If your code runs in a browser tab → use
clipboardorclipboard-polyfill. If it runs in a terminal or Electron main process → useclipboardy.
Let’s look at the simplest common task: copying a string to the clipboard.
clipboard (browser)Designed as a minimal wrapper. You create an instance tied to a DOM element (usually a button), and it handles the rest:
import ClipboardJS from 'clipboard';
// Attach to a button with data-clipboard-text
const btn = document.querySelector('#copy-btn');
const clipboard = new ClipboardJS(btn);
// Or specify text dynamically
const clipboard2 = new ClipboardJS('.btn', {
text: () => 'Hello, clipboard!'
});
clipboard2.on('success', e => {
console.log('Copied:', e.text);
e.clearSelection();
});
It automatically chooses between navigator.clipboard.writeText() (modern) and document.execCommand('copy') (legacy).
clipboard-polyfill (browser)Provides a function-based API that mimics the standard Clipboard API, with built-in polyfill logic:
import { writeText } from 'clipboard-polyfill';
async function copyToClipboard() {
try {
await writeText('Hello, clipboard!');
console.log('Copied successfully');
} catch (err) {
console.error('Failed to copy:', err);
}
}
// Must be called in response to a user gesture (e.g., click handler)
document.getElementById('copy-btn').addEventListener('click', copyToClipboard);
Under the hood, it handles permission requests, iframe sandboxing, and fallbacks seamlessly.
clipboardy (Node.js)Simple promise-based API for scripts:
import { write } from 'clipboardy';
await write('Hello, clipboard!');
console.log('Text copied to system clipboard');
Works in any Node.js context — great for CLI tools that need to output something directly to the clipboard.
copy-paste (Node.js, deprecated)Used a callback-style API:
// ⚠️ DO NOT USE — shown for reference only
const copyPaste = require('copy-paste');
copyPaste.copy('Hello, clipboard!', () => {
console.log('Copied');
});
But this package is no longer maintained and fails on newer OS versions due to changes in clipboard utilities.
Not all packages support reading clipboard content — and for good reason: browsers restrict read access for security.
clipboard: ❌ Does not support reading. Only writing.clipboard-polyfill: ✅ Supports reading via readText(), but only in secure contexts (HTTPS) and only when triggered by a user gesture. Falls back gracefully where unsupported.import { readText } from 'clipboard-polyfill';
async function readFromClipboard() {
try {
const text = await readText();
console.log('Pasted:', text);
} catch (err) {
console.error('Read failed:', err);
}
}
clipboardy: ✅ Full read/write support in Node.js:import { read, write } from 'clipboardy';
await write('test');
const content = await read(); // returns 'test'
copy-paste: Supported reading via paste(), but again — deprecated and unreliable.Browsers require clipboard write operations to occur within a user-initiated event (like a click). All browser packages respect this, but they handle violations differently.
clipboard: Silently fails if not triggered by a user action. No error thrown in legacy mode.clipboard-polyfill: Throws a clear error in development if called outside a user gesture, helping catch bugs early.clipboardy: No such restriction in Node.js — you can read/write anytime.Also, clipboard-polyfill properly handles the Permissions API for clipboard access in modern browsers, requesting permission when needed.
Most apps only need plain text, but some require richer content.
clipboard: Plain text only. No support for HTML or other formats.clipboard-polyfill: Supports writing HTML and custom MIME types via write():import { write } from 'clipboard-polyfill';
const dt = new DataTransfer();
dt.setData('text/plain', 'Fallback text');
dt.setData('text/html', '<b>Bold text</b>');
await write(dt);
This aligns with the official Clipboard API spec.
clipboardy: Plain text only. Binary or rich content isn’t supported.copy-paste: Plain text only (and broken).copy-pasteThe copy-paste package has been deprecated by its author. Its last update was years ago, it uses unsafe child_process.exec calls without proper escaping, and it fails on modern systems (e.g., macOS Sonoma removed pbcopy from default PATH in some contexts). The README now recommends using clipboardy instead. Do not use copy-paste in new projects.
You’re building a dashboard with a button that copies an API key to the clipboard.
clipboard or clipboard-polyfillclipboard. If you need better error handling, future-proofing, or plan to read clipboard later → clipboard-polyfill.// With clipboard-polyfill (recommended for new apps)
import { writeText } from 'clipboard-polyfill';
function CopyButton({ text }) {
const handleClick = () => writeText(text);
return <button onClick={handleClick}>Copy</button>;
}
You’re writing a Node.js script that generates a config snippet and puts it in the clipboard.
clipboardy#!/usr/bin/env node
import { write } from 'clipboardy';
const config = `API_KEY=${process.env.API_KEY}`;
await write(config);
console.log('Config copied to clipboard!');
You must support older browsers in a corporate environment.
clipboard-polyfillexecCommand and handles quirks in older browsers better than clipboard.You want users to paste formatted text and capture it.
clipboard-polyfill (for read() support)paste events and extract data from event.clipboardData for full control — but readText() can help in simpler cases.| Package | Environment | Write Text | Read Text | Rich Content | Actively Maintained |
|---|---|---|---|---|---|
clipboard | Browser | ✅ | ❌ | ❌ | ✅ |
clipboard-polyfill | Browser | ✅ | ✅* | ✅ | ✅ |
clipboardy | Node.js | ✅ | ✅ | ❌ | ✅ |
copy-paste | Node.js | ✅ (broken) | ✅ (broken) | ❌ | ❌ (deprecated) |
* readText() requires user gesture and secure context.
clipboard-polyfill. It’s standards-aligned, handles edge cases, and prepares you for future clipboard features.clipboard is acceptable if you don’t need read access or advanced error reporting.clipboardy is the only sane choice.copy-paste — it’s a relic.Choose based on your runtime first, then your feature needs. Don’t overcomplicate it — most web apps only need to write plain text, and both clipboard and clipboard-polyfill do that well. But if you care about maintainability and forward compatibility, clipboard-polyfill is the smarter long-term bet for browser-based clipboard interaction.
Choose clipboard if you're building a browser-based UI component (like a 'copy' button) and want a lightweight, zero-dependency wrapper around the modern navigator.clipboard API with automatic fallback to legacy execCommand for older browsers. It’s ideal for simple copy operations triggered by user gestures in web apps, but it doesn’t support reading from the clipboard or Node.js environments.
Choose clipboard-polyfill if you need robust cross-browser clipboard write (and limited read) support with automatic feature detection and polyfilling, including handling of permissions and sandboxed iframes. It’s well-suited for applications that must work reliably in enterprise environments with older browsers or strict security policies, and it supports writing plain text, HTML, and custom formats via the standard Clipboard API shape.
Choose clipboardy if you’re writing scripts or tools that run in Node.js (e.g., CLI utilities, build scripts, or Electron main processes) and need to interact with the system clipboard outside the browser. It does not work in browser environments and should never be bundled into frontend code. It supports both reading and writing clipboard content on macOS, Windows, and Linux.
Avoid copy-paste in new projects — it is deprecated and unmaintained. The package hasn’t been updated in years, relies on outdated Node.js child process patterns, and lacks support for modern security practices. Use clipboardy instead for Node.js clipboard needs.
Modern copy to clipboard. No Flash. Just 3kb gzipped.
Copying text to the clipboard shouldn't be hard. It shouldn't require dozens of steps to configure or hundreds of KBs to load. But most of all, it shouldn't depend on Flash or any bloated framework.
That's why clipboard.js exists.
You can get it on npm.
npm install clipboard --save
Or if you're not into package management, just download a ZIP file.
First, include the script located on the dist folder or load it from a third-party CDN provider.
<script src="dist/clipboard.min.js"></script>
Now, you need to instantiate it by passing a DOM selector, HTML element, or list of HTML elements.
new ClipboardJS('.btn');
Internally, we need to fetch all elements that matches with your selector and attach event listeners for each one. But guess what? If you have hundreds of matches, this operation can consume a lot of memory.
For this reason we use event delegation which replaces multiple event listeners with just a single listener. After all, #perfmatters.
We're living a declarative renaissance, that's why we decided to take advantage of HTML5 data attributes for better usability.
A pretty common use case is to copy content from another element. You can do that by adding a data-clipboard-target attribute in your trigger element.
The value you include on this attribute needs to match another's element selector.
<!-- Target -->
<input id="foo" value="https://github.com/zenorocha/clipboard.js.git" />
<!-- Trigger -->
<button class="btn" data-clipboard-target="#foo">
<img src="assets/clippy.svg" alt="Copy to clipboard" />
</button>
Additionally, you can define a data-clipboard-action attribute to specify if you want to either copy or cut content.
If you omit this attribute, copy will be used by default.
<!-- Target -->
<textarea id="bar">Mussum ipsum cacilds...</textarea>
<!-- Trigger -->
<button class="btn" data-clipboard-action="cut" data-clipboard-target="#bar">
Cut to clipboard
</button>
As you may expect, the cut action only works on <input> or <textarea> elements.
Truth is, you don't even need another element to copy its content from. You can just include a data-clipboard-text attribute in your trigger element.
<!-- Trigger -->
<button
class="btn"
data-clipboard-text="Just because you can doesn't mean you should — clipboard.js"
>
Copy to clipboard
</button>
There are cases where you'd like to show some user feedback or capture what has been selected after a copy/cut operation.
That's why we fire custom events such as success and error for you to listen and implement your custom logic.
var clipboard = new ClipboardJS('.btn');
clipboard.on('success', function (e) {
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
e.clearSelection();
});
clipboard.on('error', function (e) {
console.error('Action:', e.action);
console.error('Trigger:', e.trigger);
});
For a live demonstration, go to this site and open your console.
Each application has different design needs, that's why clipboard.js does not include any CSS or built-in tooltip solution.
The tooltips you see on the demo site were built using GitHub's Primer. You may want to check that out if you're looking for a similar look and feel.
If you don't want to modify your HTML, there's a pretty handy imperative API for you to use. All you need to do is declare a function, do your thing, and return a value.
For instance, if you want to dynamically set a target, you'll need to return a Node.
new ClipboardJS('.btn', {
target: function (trigger) {
return trigger.nextElementSibling;
},
});
If you want to dynamically set a text, you'll return a String.
new ClipboardJS('.btn', {
text: function (trigger) {
return trigger.getAttribute('aria-label');
},
});
For use in Bootstrap Modals or with any other library that changes the focus you'll want to set the focused element as the container value.
new ClipboardJS('.btn', {
container: document.getElementById('modal'),
});
Also, if you are working with single page apps, you may want to manage the lifecycle of the DOM more precisely. Here's how you clean up the events and objects that we create.
var clipboard = new ClipboardJS('.btn');
clipboard.destroy();
This library relies on both Selection and execCommand APIs. The first one is supported by all browsers while the second one is supported in the following browsers.
![]() | ![]() | ![]() | ![]() | ![]() | ![]() |
|---|---|---|---|---|---|
| 42+ ✔ | 12+ ✔ | 41+ ✔ | 9+ ✔ | 29+ ✔ | 10+ ✔ |
The good news is that clipboard.js gracefully degrades if you need to support older browsers. All you have to do is show a tooltip saying Copied! when success event is called and Press Ctrl+C to copy when error event is called because the text is already selected.
You can also check if clipboard.js is supported or not by running ClipboardJS.isSupported(), that way you can hide copy/cut buttons from the UI.
A browser extension that adds a "copy to clipboard" button to every code block on GitHub, MDN, Gist, StackOverflow, StackExchange, npm, and even Medium.
Install for Chrome and Firefox.
MIT License © Zeno Rocha