Claude Skills Guide

How to Build a Chrome Extension to Highlight and Save Text

Building a Chrome extension that captures highlighted text and stores it for later retrieval is a practical project that teaches you the fundamentals of Chrome’s extension APIs. Whether you want to create a research tool, a bookmarking system, or a note-taking assistant, the ability to detect and save text selections forms the foundation for many useful extensions.

This guide walks you through building a complete Chrome extension that detects text highlights, saves them to local storage, and provides a simple interface to manage your saved snippets.

Understanding the Extension Architecture

Chrome extensions consist of several components that work together. For a highlight-and-save extension, you need:

The communication flow works like this: when a user highlights text on any webpage, the content script detects the selection and sends the highlighted text to the background script, which stores it in Chrome’s storage API. The popup then retrieves and displays these saved snippets.

Creating the Manifest File

Every Chrome extension requires a manifest.json file that declares permissions and defines the extension structure. For our highlight-save extension, we need the storage permission to persist saved text and activeTab permission to access the current page’s content.

{
  "manifest_version": 3,
  "name": "Text Saver",
  "version": "1.0",
  "description": "Highlight and save text from any webpage",
  "permissions": [
    "storage",
    "activeTab"
  ],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icon.png"
  },
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": ["content.js"]
  }]
}

This manifest declares version 3 of Chrome’s extension API, specifies the background service worker, defines a popup interface, and configures the content script to run on all websites.

Implementing the Content Script

The content script runs in the context of web pages and detects when users select text. We listen for the mouseup event, which fires after the user releases the mouse button following a selection.

// content.js
document.addEventListener('mouseup', function(event) {
  const selection = window.getSelection();
  const selectedText = selection.toString().trim();
  
  if (selectedText.length > 0) {
    // Create a notification that text was captured
    showSaveNotification(selectedText);
  }
});

function showSaveNotification(text) {
  // Create a floating button near the selection
  const button = document.createElement('button');
  button.textContent = 'Save';
  button.className = 'save-text-btn';
  button.style.cssText = `
    position: absolute;
    z-index: 999999;
    padding: 6px 12px;
    background: #4285f4;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-family: sans-serif;
    font-size: 12px;
  `;
  
  // Position the button near the selection
  const range = window.getSelection().getRangeAt(0);
  const rect = range.getBoundingClientRect();
  button.style.top = `${window.scrollY + rect.bottom + 5}px`;
  button.style.left = `${window.scrollX + rect.left}px`;
  
  // Handle save action
  button.addEventListener('click', function() {
    chrome.runtime.sendMessage({
      action: 'saveHighlight',
      text: text,
      url: window.location.href,
      title: document.title,
      timestamp: Date.now()
    });
    button.remove();
    alert('Text saved!');
  });
  
  document.body.appendChild(button);
  
  // Remove button if user clicks elsewhere
  setTimeout(() => {
    if (document.body.contains(button)) {
      button.remove();
    }
  }, 3000);
}

This script shows a “Save” button near the highlighted text when the user releases the mouse. Clicking the button sends the selected text along with metadata (URL, page title, timestamp) to the background script for storage.

Building the Background Service Worker

The background script handles storage operations and serves as the bridge between content scripts and the popup interface. Service workers in manifest v3 are event-driven and run independently of any webpage.

// background.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === 'saveHighlight') {
    const newHighlight = {
      id: Date.now(),
      text: message.text,
      url: message.url,
      title: message.title,
      timestamp: message.timestamp
    };
    
    chrome.storage.local.get(['highlights'], function(result) {
      const highlights = result.highlights || [];
      highlights.unshift(newHighlight); // Add to beginning of array
      chrome.storage.local.set({ highlights: highlights }, function() {
        sendResponse({ success: true, highlight: newHighlight });
      });
    });
    
    return true; // Keep message channel open for async response
  }
  
  if (message.action === 'getHighlights') {
    chrome.storage.local.get(['highlights'], function(result) {
      sendResponse({ highlights: result.highlights || [] });
    });
    return true;
  }
  
  if (message.action === 'deleteHighlight') {
    chrome.storage.local.get(['highlights'], function(result) {
      const highlights = result.highlights || [];
      const filtered = highlights.filter(h => h.id !== message.id);
      chrome.storage.local.set({ highlights: filtered }, function() {
        sendResponse({ success: true });
      });
    });
    return true;
  }
});

This background script listens for three message types: saving new highlights, retrieving all saved highlights, and deleting specific highlights. It uses Chrome’s local storage API, which persists data even after the browser closes.

Creating the Popup Interface

The popup provides the user interface for viewing and managing saved text. It appears when clicking the extension icon in the Chrome toolbar.

<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
  <style>
    body { width: 320px; padding: 16px; font-family: sans-serif; }
    h2 { margin-top: 0; font-size: 16px; }
    .highlight-item {
      border-bottom: 1px solid #eee;
      padding: 12px 0;
    }
    .highlight-text {
      font-size: 14px;
      line-height: 1.4;
      margin-bottom: 8px;
    }
    .highlight-meta {
      font-size: 11px;
      color: #666;
    }
    .highlight-url {
      color: #4285f4;
      text-decoration: none;
    }
    .delete-btn {
      background: #d93025;
      color: white;
      border: none;
      padding: 4px 8px;
      border-radius: 4px;
      cursor: pointer;
      font-size: 11px;
      margin-top: 8px;
    }
    .empty-state {
      color: #666;
      text-align: center;
      padding: 20px;
    }
  </style>
</head>
<body>
  <h2>Saved Highlights</h2>
  <div id="highlights-list"></div>
  <script src="popup.js"></script>
</body>
</html>

Managing Highlights in the Popup

The popup JavaScript retrieves saved highlights from storage and renders them in a scrollable list. Each highlight shows the saved text, the source page, and a delete button.

// popup.js
document.addEventListener('DOMContentLoaded', function() {
  loadHighlights();
});

function loadHighlights() {
  chrome.runtime.sendMessage({ action: 'getHighlights' }, function(response) {
    const container = document.getElementById('highlights-list');
    const highlights = response.highlights || [];
    
    if (highlights.length === 0) {
      container.innerHTML = '<div class="empty-state">No highlights saved yet. Highlight text on any page and click Save.</div>';
      return;
    }
    
    container.innerHTML = highlights.map(highlight => `
      <div class="highlight-item">
        <div class="highlight-text">${escapeHtml(highlight.text)}</div>
        <div class="highlight-meta">
          <a href="${escapeHtml(highlight.url)}" class="highlight-url" target="_blank">${escapeHtml(highlight.title)}</a>
        </div>
        <button class="delete-btn" data-id="${highlight.id}">Delete</button>
      </div>
    `).join('');
    
    // Attach delete handlers
    container.querySelectorAll('.delete-btn').forEach(btn => {
      btn.addEventListener('click', function() {
        deleteHighlight(parseInt(this.dataset.id));
      });
    });
  });
}

function deleteHighlight(id) {
  chrome.runtime.sendMessage({ action: 'deleteHighlight', id: id }, function() {
    loadHighlights();
  });
}

function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

Loading Your Extension

To test the extension in Chrome, navigate to chrome://extensions/ and enable Developer mode in the top right corner. Click “Load unpacked” and select the folder containing your extension files. The extension icon should appear in your toolbar.

When you highlight text on any webpage, a “Save” button appears near your selection. Click it to store the text, then click the extension icon to view your saved highlights in a list.

Extending the Functionality

This basic implementation provides a foundation you can build upon. Consider adding features like search functionality to find saved highlights, tags or categories for organization, sync across devices using Chrome’s sync storage, export capabilities to save highlights as JSON or Markdown, or keyboard shortcuts for faster text capture.

Chrome’s storage API supports both local storage (limited to the device) and sync storage (synchronized across all your Chrome instances where you’re signed in). Switching to sync storage requires changing chrome.storage.local to chrome.storage.sync in your background script.

The highlight detection approach demonstrated here works well for most use cases. For more complex requirements, you might explore the Selection API’s finer-grained control over text ranges or implement context menus for additional capture options.

Built by theluckystrike — More at zovo.one