clipboardWrite Permission

6 min read

clipboardWrite Permission

What It Grants

Allows the extension to write content to the system clipboard using navigator.clipboard.writeText() or document.execCommand('copy').

Manifest

{
  "permissions": ["clipboardWrite"]
}

User Warning

“Modify data you copy and paste” — triggers a warning.

How to Write Clipboard

In Popup/Options Page

// Modern Clipboard API (preferred)
await navigator.clipboard.writeText('Hello from extension!');

// Write rich content (HTML)
const blob = new Blob(['<b>Bold text</b>'], { type: 'text/html' });
await navigator.clipboard.write([new ClipboardItem({ 'text/html': blob })]);

// Legacy method
const textarea = document.createElement('textarea');
textarea.value = 'text to copy';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
textarea.remove();

In Service Worker (MV3)

Service workers cannot write clipboard directly. Use offscreen document:

// background.ts
async function writeClipboard(text: string): Promise<void> {
  if (!(await chrome.offscreen.hasDocument())) {
    await chrome.offscreen.createDocument({
      url: chrome.runtime.getURL('offscreen.html'),
      reasons: [chrome.offscreen.Reason.CLIPBOARD],
      justification: 'Write to clipboard'
    });
  }
  await chrome.runtime.sendMessage({ type: 'WRITE_CLIPBOARD', text });
}

// offscreen.ts
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
  if (msg.type === 'WRITE_CLIPBOARD') {
    navigator.clipboard.writeText(msg.text)
      .then(() => sendResponse({ ok: true }))
      .catch(err => sendResponse({ ok: false, error: err.message }));
    return true;
  }
});

Context Menu Copy Pattern

chrome.runtime.onInstalled.addListener(() => {
  chrome.contextMenus.create({
    id: 'copy-as-markdown',
    title: 'Copy link as Markdown',
    contexts: ['link']
  });
});

chrome.contextMenus.onClicked.addListener(async (info, tab) => {
  if (info.menuItemId === 'copy-as-markdown' && info.linkUrl) {
    const markdown = `[${info.selectionText || info.linkUrl}](${info.linkUrl})`;
    await writeClipboard(markdown);
  }
});

Messaging Integration

import { createMessenger } from '@theluckystrike/webext-messaging';

type Messages = {
  COPY_TO_CLIPBOARD: { request: { text: string }; response: { ok: boolean } };
  COPY_TAB_URL: { request: { format: 'plain' | 'markdown' | 'html' }; response: { ok: boolean } };
};
const m = createMessenger<Messages>();

m.onMessage('COPY_TAB_URL', async ({ format }) => {
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  let text: string;
  switch (format) {
    case 'markdown': text = `[${tab.title}](${tab.url})`; break;
    case 'html': text = `<a href="${tab.url}">${tab.title}</a>`; break;
    default: text = tab.url || '';
  }
  await writeClipboard(text);
  return { ok: true };
});

Storage for Clipboard History

import { createStorage, defineSchema } from '@theluckystrike/webext-storage';

const schema = defineSchema({ recentCopies: 'string' }); // JSON array
const storage = createStorage(schema, 'local');

async function trackCopy(text: string) {
  const recent = JSON.parse(await storage.get('recentCopies') || '[]');
  recent.unshift({ text: text.substring(0, 200), time: Date.now() });
  if (recent.length > 50) recent.length = 50;
  await storage.set('recentCopies', JSON.stringify(recent));
}

When to Use

When NOT to Use

Permission Check

import { checkPermission } from '@theluckystrike/webext-permissions';
const granted = await checkPermission('clipboardWrite');

Cross-References

Frequently Asked Questions

How do I copy text to clipboard in a Chrome extension?

Use navigator.clipboard.writeText(‘your text’) in your content script or popup. No special permission is required for writing to clipboard in most contexts.

Can I copy images to clipboard?

Yes, but it requires the clipboardWrite permission and using the Clipboard API with image data as a Blob. —

Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.

No previous article
No next article