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                       │
│                                                     │
└─────────────────────────────────────────────────────┘
MethodPurposePermission Required
writeTextWrite textAuto-granted
readTextRead textRequired
writeWrite any dataAuto-granted
readRead any dataRequired

Master the Clipboard API to enhance your application’s interaction experience.