Claude Skills Guide

Building a Chrome extension for grocery coupon discovery requires understanding the Chrome extension APIs, web scraping techniques, and user experience patterns that make coupon finding practical. This guide covers the technical foundation for creating a functional grocery coupon finder extension.

Extension Architecture Overview

A grocery coupon finder extension typically consists of three main components: the background service worker, content scripts, and a popup interface. The background worker handles API calls and coupon storage, while content scripts inject UI elements into grocery store websites.

The manifest.json defines your extension’s capabilities:

{
  "manifest_version": 3,
  "name": "Grocery Coupon Finder",
  "version": "1.0.0",
  "permissions": ["storage", "activeTab", "scripting"],
  "host_permissions": ["*://*.grocerystore.com/*"],
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icon.png"
  }
}

Core Functionality Implementation

Coupon Detection and Extraction

Your content script needs to scan page content for product prices and match them against a coupon database. Here’s a practical approach:

// content-script.js
function detectProducts() {
  const priceSelectors = [
    '.product-price',
    '[data-price]',
    '.price-current',
    '.ProductPrice'
  ];
  
  const products = [];
  
  for (const selector of priceSelectors) {
    const elements = document.querySelectorAll(selector);
    elements.forEach(el => {
      const price = parseFloat(el.textContent.replace(/[^0-9.]/g, ''));
      const name = el.closest('.product-item')?.querySelector('.product-name')?.textContent;
      
      if (price && name) {
        products.push({ price, name, element: el });
      }
    });
  }
  
  return products;
}

Background API Integration

The background service worker manages coupon API calls and maintains a local cache:

// background.js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'findCoupons') {
    fetchCoupons(request.products)
      .then(coupons => sendResponse({ coupons }))
      .catch(error => sendResponse({ error: error.message }));
    return true;
  }
});

async function fetchCoupons(products) {
  const response = await fetch('https://api.example.com/coupons', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ products })
  });
  return response.json();
}

Coupon Data Sources

Several approaches exist for obtaining coupon data:

Direct Store APIs: Some grocery chains offer affiliate or partner APIs. Check if your local stores have developer programs.

Web Scraping Services: Services like scrapeasy or ScrapingBee provide structured coupon data from store websites.

Coupon Aggregators: APIs from coupon aggregation services offer product-level coupon matches.

// Service worker coupon matching logic
function matchCoupons(productList, availableCoupons) {
  const matches = [];
  
  for (const product of productList) {
    for (const coupon of availableCoupons) {
      if (coupon.matchesProduct(product.name) && 
          product.price >= coupon.minPurchase) {
        matches.push({
          product: product.name,
          coupon: coupon.code,
          discount: coupon.discount,
          savings: calculateSavings(product.price, coupon)
        });
      }
    }
  }
  
  return matches.sort((a, b) => b.savings - a.savings);
}

User Interface Patterns

The popup interface should provide quick access to saved coupons without distracting from the shopping experience. Consider these design patterns:

Badge Notifications: Show coupon counts directly on the extension icon using chrome.action.setBadgeText:

chrome.action.setBadgeText({ text: '3' });
chrome.action.setBadgeBackgroundColor({ color: '#4CAF50' });

Inline Overlays: Display coupon information directly on product pages using injected DOM elements:

function injectCouponBadges(products, coupons) {
  products.forEach(product => {
    const matchingCoupon = coupons.find(c => 
      c.productId === product.id
    );
    
    if (matchingCoupon) {
      const badge = document.createElement('div');
      badge.className = 'coupon-badge';
      badge.textContent = `Save $${matchingCoupon.discount}`;
      product.element.appendChild(badge);
    }
  });
}

Storage and Sync

Use chrome.storage for persistent coupon storage with cross-device sync:

// Save coupon to storage
async function saveCoupon(coupon) {
  const { savedCoupons = [] } = await chrome.storage.local.get('savedCoupons');
  
  if (!savedCoupons.find(c => c.code === coupon.code)) {
    savedCoupons.push({ ...coupon, savedAt: Date.now() });
    await chrome.storage.local.set({ savedCoupons });
  }
}

// Sync across devices
chrome.storage.sync.set({ savedCoupons: localCoupons });

Performance Considerations

Content scripts run on every page load, so optimize for efficiency:

// Debounced product scanning
function debounce(func, wait) {
  let timeout;
  return function(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(this, args), wait);
  };
}

const scanPage = debounce(() => {
  const products = detectProducts();
  chrome.runtime.sendMessage({ action: 'findCoupons', products });
}, 500);

Extension Testing

Test your extension across different scenarios:

// Test product detection
const testCases = [
  { html: '<span class="product-price">$4.99</span>', expected: 4.99 },
  { html: '<div data-price="2.50">$2.50</div>', expected: 2.50 }
];

testCases.forEach(tc => {
  document.body.innerHTML = tc.html;
  const result = detectProducts()[0].price;
  console.assert(result === tc.expected, 'Detection failed');
});

Distribution and Updates

When ready to distribute:

  1. Package your extension using chrome://extensions
  2. Create a store listing with clear screenshots
  3. Implement update checking via manifest version
  4. Track usage with anonymous analytics

Building a grocery coupon finder requires balancing functionality with performance. Focus on supporting major grocery store websites first, then expand to regional chains. The key is providing genuine value without overwhelming users with irrelevant offers.

Built by theluckystrike — More at zovo.one