# JavaScript Clipboard API Complete Guide
Master clipboard operations: reading and writing text, copying images, handling rich text, and permission management
The Clipboard API provides modern access to the clipboard. This article covers its usage and practical applications.
Basic Concepts
API Overview
// Check Clipboard API support
if ('clipboard' in navigator) {
console.log('Clipboard API available');
}
// Main methods
// navigator.clipboard.writeText() - Write text
// navigator.clipboard.readText() - Read text
// navigator.clipboard.write() - Write arbitrary data
// navigator.clipboard.read() - Read arbitrary data
Permission Requirements
// Query permission status
async function checkClipboardPermission() {
try {
// Read permission
const readPermission = await navigator.permissions.query({
name: 'clipboard-read'
});
console.log('Read permission:', readPermission.state);
// Write permission (usually auto-granted)
const writePermission = await navigator.permissions.query({
name: 'clipboard-write'
});
console.log('Write permission:', writePermission.state);
// Listen for permission changes
readPermission.addEventListener('change', () => {
console.log('Read permission changed:', readPermission.state);
});
} catch (e) {
console.error('Permission query failed:', e);
}
}
Text Operations
Copy Text
// Basic copy
async function copyText(text) {
try {
await navigator.clipboard.writeText(text);
console.log('Copy successful');
return true;
} catch (err) {
console.error('Copy failed:', err);
return false;
}
}
// Copy element content
async function copyElementText(selector) {
const element = document.querySelector(selector);
if (!element) return false;
const text = element.textContent || element.value;
return copyText(text);
}
// Copy button with feedback
class CopyButton {
constructor(button, targetSelector) {
this.button = button;
this.target = document.querySelector(targetSelector);
this.originalText = button.textContent;
button.addEventListener('click', () => this.copy());
}
async copy() {
const text = this.target.textContent || this.target.value;
try {
await navigator.clipboard.writeText(text);
this.showSuccess();
} catch (err) {
this.showError();
}
}
showSuccess() {
this.button.textContent = 'Copied!';
this.button.classList.add('success');
setTimeout(() => {
this.button.textContent = this.originalText;
this.button.classList.remove('success');
}, 2000);
}
showError() {
this.button.textContent = 'Copy failed';
this.button.classList.add('error');
setTimeout(() => {
this.button.textContent = this.originalText;
this.button.classList.remove('error');
}, 2000);
}
}
// Usage
new CopyButton(
document.querySelector('.copy-btn'),
'#code-block'
);
Read Text
// Basic read
async function readText() {
try {
const text = await navigator.clipboard.readText();
console.log('Clipboard content:', text);
return text;
} catch (err) {
console.error('Read failed:', err);
return null;
}
}
// Paste to input
async function pasteToInput(inputSelector) {
const input = document.querySelector(inputSelector);
if (!input) return;
const text = await readText();
if (text) {
input.value = text;
input.dispatchEvent(new Event('input'));
}
}
// Listen for paste event
document.addEventListener('paste', async (e) => {
// Prevent default paste
e.preventDefault();
// Get pasted text
const text = e.clipboardData.getData('text/plain');
// Or use Clipboard API
const clipboardText = await navigator.clipboard.readText();
console.log('Pasted content:', text);
});
Rich Media Operations
Copy Images
// Copy image to clipboard
async function copyImage(imageUrl) {
try {
// Fetch image data
const response = await fetch(imageUrl);
const blob = await response.blob();
// Create ClipboardItem
const item = new ClipboardItem({
[blob.type]: blob
});
await navigator.clipboard.write([item]);
console.log('Image copied successfully');
return true;
} catch (err) {
console.error('Image copy failed:', err);
return false;
}
}
// Copy from Canvas
async function copyCanvas(canvas) {
return new Promise((resolve, reject) => {
canvas.toBlob(async (blob) => {
try {
const item = new ClipboardItem({
'image/png': blob
});
await navigator.clipboard.write([item]);
resolve(true);
} catch (err) {
reject(err);
}
}, 'image/png');
});
}
// Copy screenshot
async function copyScreenshot(element) {
// Requires html2canvas library
const canvas = await html2canvas(element);
return copyCanvas(canvas);
}
Read Images
// Read image from clipboard
async function readImage() {
try {
const items = await navigator.clipboard.read();
for (const item of items) {
// Find image type
const imageType = item.types.find(type => type.startsWith('image/'));
if (imageType) {
const blob = await item.getType(imageType);
return URL.createObjectURL(blob);
}
}
return null;
} catch (err) {
console.error('Read image failed:', err);
return null;
}
}
// Display pasted image
async function displayPastedImage(containerSelector) {
const container = document.querySelector(containerSelector);
const imageUrl = await readImage();
if (imageUrl) {
const img = document.createElement('img');
img.src = imageUrl;
container.appendChild(img);
}
}
// Listen for image paste
document.addEventListener('paste', async (e) => {
const items = e.clipboardData.items;
for (const item of items) {
if (item.type.startsWith('image/')) {
const blob = item.getAsFile();
const url = URL.createObjectURL(blob);
// Display image
const img = document.createElement('img');
img.src = url;
document.body.appendChild(img);
}
}
});
Copy Rich Text
// Copy HTML content
async function copyHtml(html, plainText) {
try {
const htmlBlob = new Blob([html], { type: 'text/html' });
const textBlob = new Blob([plainText], { type: 'text/plain' });
const item = new ClipboardItem({
'text/html': htmlBlob,
'text/plain': textBlob
});
await navigator.clipboard.write([item]);
return true;
} catch (err) {
console.error('Copy failed:', err);
return false;
}
}
// Copy formatted table
async function copyTable(tableElement) {
const html = tableElement.outerHTML;
// Generate plain text version
const rows = tableElement.querySelectorAll('tr');
const plainText = Array.from(rows)
.map(row => {
const cells = row.querySelectorAll('td, th');
return Array.from(cells).map(cell => cell.textContent).join('\t');
})
.join('\n');
return copyHtml(html, plainText);
}
// Read rich text
async function readHtml() {
try {
const items = await navigator.clipboard.read();
for (const item of items) {
if (item.types.includes('text/html')) {
const blob = await item.getType('text/html');
return await blob.text();
}
}
return null;
} catch (err) {
console.error('Read failed:', err);
return null;
}
}
Practical Applications
Code Copy Widget
class CodeCopyWidget {
constructor(container) {
this.container = container;
this.codeBlocks = container.querySelectorAll('pre code');
this.init();
}
init() {
this.codeBlocks.forEach(block => {
this.addCopyButton(block.parentElement);
});
}
addCopyButton(pre) {
const wrapper = document.createElement('div');
wrapper.className = 'code-wrapper';
const button = document.createElement('button');
button.className = 'copy-code-btn';
button.innerHTML = `
<svg viewBox="0 0 24 24" width="16" height="16">
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
</svg>
`;
pre.parentNode.insertBefore(wrapper, pre);
wrapper.appendChild(pre);
wrapper.appendChild(button);
button.addEventListener('click', () => this.copyCode(pre, button));
}
async copyCode(pre, button) {
const code = pre.querySelector('code');
const text = code.textContent;
try {
await navigator.clipboard.writeText(text);
button.classList.add('copied');
button.innerHTML = `
<svg viewBox="0 0 24 24" width="16" height="16">
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
</svg>
`;
setTimeout(() => {
button.classList.remove('copied');
button.innerHTML = `
<svg viewBox="0 0 24 24" width="16" height="16">
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
</svg>
`;
}, 2000);
} catch (err) {
console.error('Copy failed:', err);
}
}
}
// Usage
new CodeCopyWidget(document.body);
Image Paste Uploader
class ImagePasteUploader {
constructor(options) {
this.options = {
container: document.body,
onUpload: async () => {},
onPreview: () => {},
maxSize: 5 * 1024 * 1024, // 5MB
allowedTypes: ['image/png', 'image/jpeg', 'image/gif'],
...options
};
this.init();
}
init() {
this.options.container.addEventListener('paste', (e) => {
this.handlePaste(e);
});
// Drag and drop support
this.options.container.addEventListener('drop', (e) => {
e.preventDefault();
this.handleDrop(e);
});
this.options.container.addEventListener('dragover', (e) => {
e.preventDefault();
});
}
async handlePaste(e) {
const items = e.clipboardData?.items;
if (!items) return;
for (const item of items) {
if (item.type.startsWith('image/')) {
e.preventDefault();
const file = item.getAsFile();
await this.processImage(file);
break;
}
}
}
async handleDrop(e) {
const files = e.dataTransfer?.files;
if (!files) return;
for (const file of files) {
if (file.type.startsWith('image/')) {
await this.processImage(file);
}
}
}
async processImage(file) {
// Validate
if (!this.validateImage(file)) return;
// Preview
const previewUrl = URL.createObjectURL(file);
this.options.onPreview(previewUrl);
// Upload
try {
const result = await this.options.onUpload(file);
return result;
} catch (err) {
console.error('Upload failed:', err);
}
}
validateImage(file) {
if (!this.options.allowedTypes.includes(file.type)) {
console.error('Unsupported image format');
return false;
}
if (file.size > this.options.maxSize) {
console.error('Image too large');
return false;
}
return true;
}
}
// Usage
new ImagePasteUploader({
container: document.querySelector('#editor'),
onPreview: (url) => {
const img = document.createElement('img');
img.src = url;
document.querySelector('#preview').appendChild(img);
},
onUpload: async (file) => {
const formData = new FormData();
formData.append('image', file);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
return response.json();
}
});
Clipboard History
class ClipboardHistory {
constructor(options = {}) {
this.options = {
maxItems: 20,
storageKey: 'clipboardHistory',
...options
};
this.history = this.loadHistory();
this.setupListener();
}
loadHistory() {
try {
const stored = localStorage.getItem(this.options.storageKey);
return stored ? JSON.parse(stored) : [];
} catch {
return [];
}
}
saveHistory() {
localStorage.setItem(
this.options.storageKey,
JSON.stringify(this.history)
);
}
setupListener() {
// Listen for copy events
document.addEventListener('copy', async () => {
// Delay read to ensure clipboard is updated
setTimeout(async () => {
try {
const text = await navigator.clipboard.readText();
this.addItem(text);
} catch (err) {
console.error('Failed to read clipboard:', err);
}
}, 100);
});
}
addItem(content) {
if (!content || content.trim() === '') return;
// Remove duplicates
this.history = this.history.filter(item => item.content !== content);
// Add to beginning
this.history.unshift({
content,
timestamp: Date.now(),
id: crypto.randomUUID()
});
// Limit count
if (this.history.length > this.options.maxItems) {
this.history = this.history.slice(0, this.options.maxItems);
}
this.saveHistory();
}
getHistory() {
return [...this.history];
}
async copyItem(id) {
const item = this.history.find(i => i.id === id);
if (item) {
await navigator.clipboard.writeText(item.content);
}
}
deleteItem(id) {
this.history = this.history.filter(i => i.id !== id);
this.saveHistory();
}
clearHistory() {
this.history = [];
this.saveHistory();
}
}
const clipboardHistory = new ClipboardHistory();
Share Functionality
class ShareManager {
constructor() {
this.shareData = null;
}
// Copy link share
async copyLink(url, title) {
const text = title ? `${title}\n${url}` : url;
try {
await navigator.clipboard.writeText(text);
this.showNotification('Link copied');
return true;
} catch {
return false;
}
}
// Copy rich link
async copyRichLink(url, title) {
const html = `<a href="${url}">${title}</a>`;
const text = `${title}: ${url}`;
try {
const htmlBlob = new Blob([html], { type: 'text/html' });
const textBlob = new Blob([text], { type: 'text/plain' });
await navigator.clipboard.write([
new ClipboardItem({
'text/html': htmlBlob,
'text/plain': textBlob
})
]);
return true;
} catch {
// Fallback to plain text
return this.copyLink(url, title);
}
}
// Combine with Web Share API
async share(data) {
// Prefer native share
if (navigator.share) {
try {
await navigator.share(data);
return { method: 'native', success: true };
} catch (err) {
if (err.name !== 'AbortError') {
console.error('Share failed:', err);
}
}
}
// Fallback to copy
const text = data.text || data.title || '';
const url = data.url || '';
const success = await this.copyLink(url, text);
return { method: 'clipboard', success };
}
showNotification(message) {
// Show notification
}
}
const shareManager = new ShareManager();
Compatibility Handling
Fallback Solutions
class ClipboardHelper {
// Check support
static get isSupported() {
return 'clipboard' in navigator;
}
// Copy text with fallback
static async copyText(text) {
if (this.isSupported) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch {
// API failed, use fallback
}
}
return this.fallbackCopy(text);
}
// Fallback copy method
static fallbackCopy(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.cssText = 'position:fixed;left:-9999px;top:-9999px;';
document.body.appendChild(textarea);
try {
textarea.select();
textarea.setSelectionRange(0, textarea.value.length);
const success = document.execCommand('copy');
return success;
} catch {
return false;
} finally {
document.body.removeChild(textarea);
}
}
// Read text with fallback
static async readText() {
if (this.isSupported) {
try {
return await navigator.clipboard.readText();
} catch {
// API failed
}
}
// Fallback: prompt user to use Ctrl+V
return null;
}
}
// Usage
document.querySelector('.copy-btn').addEventListener('click', async () => {
const success = await ClipboardHelper.copyText('Hello World');
console.log(success ? 'Copy successful' : 'Copy failed');
});
Best Practices Summary
Clipboard API Best Practices:
┌─────────────────────────────────────────────────────┐
│ │
│ Security Considerations │
│ ├── Access only during user interaction │
│ ├── Handle permission denied cases │
│ ├── Validate read content │
│ └── Clear sensitive data │
│ │
│ User Experience │
│ ├── Provide visual feedback │
│ ├── Support multiple formats │
│ ├── Implement fallback solutions │
│ └── Handle file size limits │
│ │
│ Compatibility │
│ ├── Check API support │
│ ├── Use execCommand fallback │
│ ├── Handle browser differences │
│ └── Test various scenarios │
│ │
└─────────────────────────────────────────────────────┘
| Method | Purpose | Permission Required |
|---|---|---|
| writeText | Write text | Auto-granted |
| readText | Read text | Required |
| write | Write any data | Auto-granted |
| read | Read any data | Required |
Master the Clipboard API to enhance your application’s interaction experience.