DEV Community

Khaled Mahmud
Khaled Mahmud

Posted on

Building a Clipboard Listener Chrome Extension in Manifest V3: What I Learned the Hard Way

When I started building ReFind — a Chrome extension that captures URLs
as you copy them — I assumed the clipboard listener would be the easy part.
It wasn't. Here's what actually happened and how I solved it.

The Goal

Every time the user copies a URL (Cmd+C / Ctrl+C on a link), the extension
should silently capture it, validate it's actually a URL, and process it.
No popup interaction required. Fully automatic.

Why Background Scripts Are Tricky in MV3

Manifest V3 replaced persistent background pages with service workers.
Unlike a background page that stays alive as long as the browser is open,
a service worker can be terminated by Chrome after ~30 seconds of inactivity.

This creates a problem: if you store anything in memory (variables, state),
it can vanish between events. The fix: persist everything to
chrome.storage.local immediately.

The Clipboard Access Problem

Here's the tricky part: you can't directly access navigator.clipboard
from a service worker context. Chrome restricts clipboard access to require
an active user gesture (a click, keypress, etc.) — and service workers
don't have a DOM or event context for this.

The solution I landed on:

  1. Listen for the copy keyboard shortcut using chrome.commands API
  2. Inject a content script into the active tab (which has DOM access)
  3. Have the content script read the clipboard and message the background script with the result

Content script → background script communication via chrome.runtime.sendMessage().

URL Validation

Once I had clipboard access working, I needed to filter for URLs only.
A simple regex check before processing:

const isUrl = /^https?:\/\/.+/.test(text);

This prevents capturing phone numbers, random copied text, passwords, etc.

What I'd Do Differently

If I were starting over, I'd add the URL validation earlier in the pipeline —
ideally at the content script level before sending to the background. Less
round-tripping, and it prevents unnecessary message passing.

The one thing I underestimated: the permission model in MV3 is strict and
intentionally so. Read the permission docs carefully before you assume
something will just work.

Questions? I'm happy to go deeper on any part of this.

Top comments (0)