Chrome Extension Clipboard API — How to Copy and Paste Programmatically

8 min read

Clipboard API Guide

Overview

Using the Clipboard API (Modern)

The navigator.clipboard API provides a modern, promise-based interface for clipboard operations. This is the recommended approach for content scripts and pages where the API is available.

// Copy text to clipboard
async function copyText(text) {
  try {
    await navigator.clipboard.writeText(text);
    console.log('Text copied successfully');
  } catch (err) {
    console.error('Failed to copy:', err);
  }
}

// Read text from clipboard
async function pasteText() {
  try {
    const text = await navigator.clipboard.readText();
    console.log('Pasted text:', text);
    return text;
  } catch (err) {
    console.error('Failed to read clipboard:', err);
  }
}

// Copy HTML to clipboard
async function copyHtml(html, plainText) {
  const item = new ClipboardItem({
    'text/html': new Blob([html], { type: 'text/html' }),
    'text/plain': new Blob([plainText], { type: 'text/plain' })
  });
  await navigator.clipboard.write([item]);
}

Permissions

For clipboard operations, permissions requirements vary based on your extension’s architecture and target use cases.

Manifest V3 Permissions

{
  "permissions": [
    "clipboardRead",
    "clipboardWrite"
  ]
}

The clipboardWrite permission allows your extension to write to the clipboard without user interaction. The clipboardRead permission requires explicit user gesture (such as a click) to read clipboard contents, unless your extension is the currently active tab.

Host Permissions

If you need clipboard access for specific websites, you can request host permissions:

{
  "host_permissions": [
    "https://*.example.com/*"
  ]
}

execCommand Fallback

For older browsers or environments where navigator.clipboard isn’t available, use the legacy document.execCommand() approach. This method requires a visible textarea or input element.

function fallbackCopy(text) {
  const textarea = document.createElement('textarea');
  textarea.value = text;
  textarea.style.position = 'fixed';
  textarea.style.left = '-9999px';
  document.body.appendChild(textarea);
  
  textarea.select();
  textarea.focus();
  
  let success = false;
  try {
    success = document.execCommand('copy');
  } catch (err) {
    console.error('Fallback copy failed:', err);
  }
  
  document.body.removeChild(textarea);
  return success;
}

function fallbackPaste() {
  return new Promise((resolve, reject) => {
    const textarea = document.createElement('textarea');
    textarea.style.position = 'fixed';
    textarea.style.left = '-9999px';
    document.body.appendChild(textarea);
    
    textarea.select();
    textarea.focus();
    
    try {
      const text = textarea.value;
      document.body.removeChild(textarea);
      resolve(text);
    } catch (err) {
      document.body.removeChild(textarea);
      reject(err);
    }
  });
}

Offscreen Documents in Manifest V3

In Manifest V3, service workers cannot access the DOM directly, which means navigator.clipboard isn’t available in the background script. Use offscreen documents to perform clipboard operations that require DOM access.

Creating an Offscreen Document

// In your background service worker
async function copyWithOffscreen(text) {
  // Check if an offscreen document is already open
  const existingContexts = await chrome.runtime.getContexts({
    contextTypes: ['OFFSCREEN_DOCUMENT']
  });
  
  if (existingContexts.length === 0) {
    // Create a new offscreen document
    await chrome.offscreen.createDocument({
      url: 'offscreen.html',
      reasons: ['CLIPBOARD'],
      justification: 'Copy text to clipboard from background script'
    });
  }
  
  // Send message to the offscreen document
  const response = await chrome.runtime.sendMessage({
    target: 'offscreen',
    action: 'copy',
    text: text
  });
  
  return response.success;
}

Offscreen Document Handler

// In offscreen.html
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === 'copy') {
    navigator.clipboard.writeText(message.text)
      .then(() => sendResponse({ success: true }))
      .catch((err) => sendResponse({ success: false, error: err.message }));
    return true; // Keep message channel open for async response
  }
  
  if (message.action === 'paste') {
    navigator.clipboard.readText()
      .then((text) => sendResponse({ success: true, text: text }))
      .catch((err) => sendResponse({ success: false, error: err.message }));
    return true;
  }
});

Security Considerations

When working with clipboard operations in Chrome extensions, several security concerns must be addressed to protect user data and maintain trust.

User Gesture Requirements

The Clipboard API requires a user gesture (such as a click or keypress) for read operations in most contexts. This prevents malicious extensions from silently reading sensitive data from the clipboard. Always trigger clipboard read operations in response to explicit user actions.

Data Validation

Always validate and sanitize clipboard data before using it:

async function safePaste() {
  const text = await navigator.clipboard.readText();
  
  // Validate the data
  if (!text || typeof text !== 'string') {
    return null;
  }
  
  // Sanitize if needed (e.g., remove potentially dangerous content)
  const sanitized = text.slice(0, 10000); // Limit length
  
  return sanitized;
}

Sensitive Data Handling

Be cautious when writing sensitive data to the clipboard. Consider implementing a feature that clears clipboard data after a timeout:

async function copySensitiveData(data, clearAfterMs = 30000) {
  await navigator.clipboard.writeText(data);
  
  // Clear clipboard after timeout
  setTimeout(async () => {
    try {
      const current = await navigator.clipboard.readText();
      if (current === data) {
        await navigator.clipboard.writeText('');
      }
    } catch (err) {
      // Ignore errors during cleanup
    }
  }, clearAfterMs);
}

Cross-Origin Restrictions

Clipboard operations are subject to cross-origin restrictions. Ensure your extension has appropriate permissions and that the page has focus when attempting to read clipboard contents.

Best Practices

Follow these recommendations for reliable clipboard functionality across different extension contexts:

No previous article
No next article