DEV Community

Anurag Gupta
Anurag Gupta

Posted on

The Clipboard Problem That Drove Me to Build My Own Solution

How losing the same code snippet 50 times led me down a rabbit hole

I lost my clipboard again. I'd copied a complex SQL query, switched to another tab, copied something else, and the query was gone. I had to rebuild it from memory.

This kept happening. As a remote worker constantly switching between my laptop and desktop, I'd copy something on one device, switch to the other, and it was gone. I tried a few clipboard managers, but they didn't fit my workflow.

So I built my own. Here's what I learned, the challenges I hit, and what I'd do differently.


The Problem (Or: Why I Got Frustrated)

The Daily Frustration

  • Copy a code snippet on my laptop, switch to desktop, and it's gone
  • Copy an email template, switch tabs, copy something else, and lose the template
  • Scroll through 50 clipboard items to find the one I use daily

Why Existing Solutions Didn't Work for Me

I tried several tools:

  1. Built-in OS clipboard managers - Don't sync across devices
  2. Cloud clipboard tools - Too slow, clunky, or required too many steps
  3. Browser extensions - Saved everything but didn't prioritize what I actually used

Most extensions just saved history. I'd have to scroll through dozens of items to find the email signature I paste daily. That defeated the purpose.


What I Built

I built a browser extension that:

  • Syncs clipboard history across Chrome and Edge browsers
  • Learns what I paste most and prioritizes those items
  • Provides a right-click context menu for quick access
  • Works on complex sites like Google Docs and Reddit

The key difference: it tracks what I actually paste, not what I click. Items I paste frequently automatically move to the top.


The Technical Challenges

Clipboard API Limitations

The Clipboard API has quirks:

// This works in some contexts, not others
navigator.clipboard.readText().then(text => {
  // But what if the page doesn't have focus?
  // What if it's a content script?
});
Enter fullscreen mode Exit fullscreen mode

Issues:

  • Requires HTTPS or localhost
  • Content scripts can't always access clipboard directly
  • Permission handling varies by context

Solution: Message passing between content scripts and background service worker, with fallbacks.

Manifest V3 Gotchas

Service workers can be killed anytime:

// This state gets lost when the service worker dies
let clipboardHistory = [];

// Had to persist everything
chrome.storage.local.set({ clipboardHistory });
Enter fullscreen mode Exit fullscreen mode

Challenges:

  • Service worker lifecycle is unpredictable
  • Content script injection timing issues
  • CSP restrictions block some approaches

Solution: Persist state in chrome.storage.local and handle re-initialization.

Paste Functionality on Complex Sites

Pasting into Google Docs, Reddit, or Notion is tricky:

// Method 1: Modern approach (doesn't always work)
element.dispatchEvent(new ClipboardEvent('paste'));

// Method 2: Deprecated but sometimes the only option
document.execCommand('paste');

// Method 3: Direct DOM manipulation (works on simple sites)
element.textContent = clipboardText;
Enter fullscreen mode Exit fullscreen mode

I ended up trying multiple methods in sequence:

async function pasteText(element, text) {
  // Try modern approach first
  try {
    element.focus();
    await navigator.clipboard.writeText(text);
    element.dispatchEvent(new ClipboardEvent('paste'));
    return true;
  } catch (e) {
    // Fallback to execCommand
    try {
      element.focus();
      document.execCommand('insertText', false, text);
      return true;
    } catch (e2) {
      // Last resort: direct manipulation
      element.textContent = text;
      return true;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Real-Time Sync Without Constant Polling

I wanted changes to appear instantly across devices without polling:

// Polling approach (what I wanted to avoid)
setInterval(async () => {
  const latest = await fetchLatestClipboard();
  // Check for changes...
}, 5000); // Too slow, or too resource-intensive
Enter fullscreen mode Exit fullscreen mode

Solution: Real-time subscriptions (using Supabase in my case):

const subscription = supabase
  .channel('clipboard-changes')
  .on('postgres_changes', {
    event: 'INSERT',
    schema: 'public',
    table: 'clipboard_items'
  }, (payload) => {
    // Handle new item instantly
    updateLocalClipboard(payload.new);
  })
  .subscribe();
Enter fullscreen mode Exit fullscreen mode

Challenges:

  • Handling connection drops
  • Resolving conflicts when same item updated on multiple devices
  • Managing subscription lifecycle

Cross-Browser Compatibility

Chrome, Firefox, and Edge have differences:

// Chrome uses chrome.*
chrome.storage.local.get('key', callback);

// Firefox uses browser.*
browser.storage.local.get('key').then(callback);

// Edge uses chrome.* but behaves slightly differently
Enter fullscreen mode Exit fullscreen mode

Solution: Abstraction layer:

const browserAPI = {
  storage: {
    local: {
      get: (key) => {
        if (typeof chrome !== 'undefined' && chrome.storage) {
          return new Promise(resolve => {
            chrome.storage.local.get(key, resolve);
          });
        } else if (typeof browser !== 'undefined') {
          return browser.storage.local.get(key);
        }
      },
      // ... similar for set, remove, etc.
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

Smart Prioritization

I wanted frequently used items to surface automatically. This required:

  • Tracking usage when items are actually pasted
  • Syncing usage data across devices
  • Efficient sorting and filtering

The implementation involves tracking paste events and maintaining usage statistics, but the core concept is prioritizing items based on actual usage patterns rather than just recency.


What I Learned

1. Test on Real Sites Early

Don't assume paste works everywhere. Test on Google Docs, Reddit, Notion, CodePen, etc.

2. Service Worker State is Ephemeral

Persist everything. Assume the service worker can die at any moment.

3. Real-Time Sync is Harder Than It Looks

Connection drops, conflicts, edge cases - it's more complex than it seems. Start simple and iterate.

4. User Behavior is Unpredictable

Track what users actually do, not what you think they do. I track pastes, not clicks.

5. Cross-Browser Testing is Essential

Chrome, Firefox, and Edge behave differently. Test all of them.


What I'd Do Differently

  1. Start with simpler MVP - Local-only first, then add sync
  2. Better conflict resolution - Should have planned this earlier
  3. More error handling - Add retry logic from the start
  4. Better testing - Should have built testing infrastructure earlier
  5. Get user feedback sooner - Built in isolation too long

Open Questions

I'm still figuring out:

  1. How do you handle clipboard permissions in extensions reliably?
  2. Better approaches for paste compatibility across complex sites?
  3. How would you architect real-time sync for this use case?
  4. Manifest V3 service worker lifecycle - best practices?
  5. How do you test extensions across browsers efficiently?

If you've built something similar or have ideas, I'd love to hear them.


The Result

After a few months of using it, it's working well. I rarely lose clipboard content, and frequently used items appear quickly. It's not perfect, but it fits my workflow.

If you want to try it, it's available for Chrome and Edge. More importantly, I'd love to hear:

  • How you've solved similar problems
  • What challenges you've faced with browser extensions
  • What features would make this genuinely useful

This is still a work in progress, and I'm learning as I go.


Author: Anurag G.
LinkedIn: https://www.linkedin.com/in/anu95

Top comments (0)