Simple module exposing an async copy function that uses the Async Clipboard API (navigator.clipboard) in secure contexts (HTTPS / localhost), with automatic fallback to document.execCommand('copy') for non-secure contexts or older browsers.
import copy from 'copy-to-clipboard';
await copy('Text');
// Copy with options
await copy('Text', {
debug: true,
message: 'Press #{key} to copy',
});
// Copy as HTML (text/html + text/plain written simultaneously)
await copy('<b>Hello <i>world</i></b>', { format: 'text/html' });
// Custom plain-text fallback via onCopy
await copy('<b>Hello <i>world</i></b>', {
format: 'text/html',
onCopy: () => new ClipboardItem({
'text/html': new Blob(['<b>Hello <i>world</i></b>'], { type: 'text/html' }),
'text/plain': new Blob(['Hello world'], { type: 'text/plain' }),
}),
});copy(text: string, options?: object): Promise<boolean> — copies text to clipboard. Returns true on success, false if all paths failed (no additional keystrokes were required from the user).
v4 breaking change:
copy()is now async and returnsPromise<boolean>. Useawait copy(...)to get the result.
| Value | Default | Notes |
|---|---|---|
options.debug |
false |
Boolean. Enable output to console. |
options.message |
'Copy to clipboard: #{key}, Enter' |
String. Prompt message used when fallbackToPrompt is enabled. All occurrences of #{key} are replaced with ⌘+C on macOS or Ctrl+C otherwise. |
options.format |
— | String. MIME type to copy as. Use 'text/html' to copy rich text; 'text/plain' to strip inherited styles when pasting into rich-text editors. When set alongside 'text/html', both text/html and text/plain are written simultaneously via ClipboardItem. |
options.onCopy |
— | (clipboardData: ClipboardItem | DataTransfer) => ClipboardItem | void. Called before the write. On the async path, receives the constructed ClipboardItem and may return a new one to replace it (useful for custom MIME types or a different plain-text fallback). On the execCommand fallback path, receives the DataTransfer object; return value is ignored. |
options.fallbackToPrompt |
false |
Boolean. If true, shows a window.prompt() as a last resort when both navigator.clipboard and execCommand fail. Off by default in v4. |
navigator.clipboard.writeText(text)— used when the page is a secure context (HTTPS / localhost),navigator.clipboardis available, and neitheroptions.formatnoroptions.onCopyis set.navigator.clipboard.write([ClipboardItem])— used in a secure context whenoptions.formatoroptions.onCopyis set. Builds aClipboardItemwithtext/plainalways present; adds the requested MIME type alongside it.onCopymay return a replacementClipboardItem.execCommand('copy')fallback — used on non-HTTPS pages, whennavigator.clipboardis unavailable, or when the async write throws. Uses a hidden<span>element.preventDefaultis only called whenoptions.formatis set.window.prompt()fallback — opt-in viaoptions.fallbackToPrompt: true.
By default, copy(html, { format: 'text/html' }) puts the raw HTML string in the text/plain slot of the ClipboardItem. If you want apps that only accept plain text to receive readable content instead of markup, use onCopy to supply a stripped version:
function stripHtml(html) {
const div = document.createElement('div');
div.innerHTML = html;
return div.textContent || div.innerText || '';
}
const html = '<b>Hello <i>world</i></b>';
await copy(html, {
format: 'text/html',
onCopy: () => new ClipboardItem({
'text/html': new Blob([html], { type: 'text/html' }),
'text/plain': new Blob([stripHtml(html)], { type: 'text/plain' }),
}),
});
// text/html → '<b>Hello <i>world</i></b>'
// text/plain → 'Hello world'await copy('col1,col2\nval1,val2', {
format: 'text/csv',
});
// text/csv → 'col1,col2\nval1,val2'
// text/plain → 'col1,col2\nval1,val2' (always included as fallback)| Browser | Minimum | Notes |
|---|---|---|
| Chrome | 76+ | Full ClipboardItem + write() support |
| Firefox | 127+ | Full ClipboardItem + write() landed in Firefox 127 (mid-2024) |
| Safari | 13.1+ | writeText() and write() available |
| Edge | 79+ | Chromium-based; same as Chrome |
execCommand fallback retains support for non-HTTPS contexts and any browser that reaches the catch path.
Note: The async clipboard write must occur within a user gesture (click, keydown, etc.). This library is designed to be called from event handlers, so this constraint is normally satisfied automatically.
npm i copy-to-clipboard
Available as CommonJS, ES module, and IIFE (for <script> tags):
// ESM
import copy from 'copy-to-clipboard';
// CommonJS
const copy = require('copy-to-clipboard');<!-- IIFE via CDN — exposes window.copyToClipboard -->
<script src="https://unpkg.com/copy-to-clipboard/dist/index.global.js"></script>Built-in declarations are included for both CommonJS and ESM consumers.
import copy from 'copy-to-clipboard';
import type { Options } from 'copy-to-clipboard';
const result: boolean = await copy('text');End-to-end tests are powered by Nightwatch using native browser drivers.
npm i
npm test # Chrome (default)
npm run test:firefox
npm run test:safari
npm run test:edge
npm run test:all # all local browsers
Safari prerequisite: enable "Allow Remote Automation" in Safari's Develop menu. See Testing with WebDriver in Safari.
Chrome, Firefox, and Edge tests run automatically on every push to master and on pull requests via GitHub Actions (Ubuntu runner, headless).
Cross-browser tests (Chrome, Firefox, Safari, Edge) run on LambdaTest automatically on every version tag (v*) and can be triggered manually from the Actions tab. Requires LT_USERNAME and LT_ACCESS_KEY repository secrets.
npm run test:lt:chrome
npm run test:lt:firefox
npm run test:lt:safari
npm run test:lt:edge
npm run test:lt:all
copy()is now async — returnsPromise<boolean>instead ofboolean. Wrap call sites withawait.- IE11 support dropped —
window.clipboardDatapath removed. window.prompt()fallback is opt-in — setoptions.fallbackToPrompt: trueto enable.- Build output moved to
dist/— directrequire('copy-to-clipboard/index.js')paths will break; use the package name only.