Claude Skills Guide

Building a Chrome Extension Price Match Finder

Creating a Chrome extension that identifies lower prices across multiple retailers is a practical project that demonstrates core extension development concepts. This guide walks through building a price match finder extension using manifest V3, with patterns you can adapt for your own projects.

Understanding the Architecture

Chrome extensions with manifest V3 follow a specific architecture. Your extension needs three primary components working together:

For a price match finder, the typical flow involves detecting product pages, extracting product identifiers, querying price APIs, and displaying savings to users.

Project Structure

A minimal extension structure looks like this:

price-match-finder/
├── manifest.json
├── background.js
├── content.js
├── popup/
│   ├── popup.html
│   └── popup.js
└── styles.css

Manifest Configuration

Your manifest.json defines capabilities and permissions:

{
  "manifest_version": 3,
  "name": "Price Match Finder",
  "version": "1.0.0",
  "description": "Find lower prices across multiple retailers",
  "permissions": [
    "activeTab",
    "scripting",
    "storage"
  ],
  "host_permissions": [
    "*://*.amazon.com/*",
    "*://*.walmart.com/*",
    "*://*.target.com/*"
  ],
  "action": {
    "default_popup": "popup/popup.html",
    "default_icon": "icon.png"
  },
  "background": {
    "service_worker": "background.js"
  }
}

The host_permissions array specifies which sites your extension can access. Add retailers relevant to your price comparison logic.

Extracting Product Information

Content scripts run in the context of web pages and can extract product data. Here’s a pattern for detecting product pages:

// content.js
const PRODUCT_SELECTORS = {
  amazon: '[data-asin]',
  walmart: '[data-product-id]',
  target: '[data-test="product-title"]'
};

function detectProductInfo() {
  const hostname = window.location.hostname;
  
  if (hostname.includes('amazon')) {
    const asin = document.querySelector('[data-asin]')?.dataset.asin;
    return asin ? { retailer: 'amazon', id: asin, type: 'asin' } : null;
  }
  
  if (hostname.includes('walmart')) {
    const productId = document.querySelector('[data-product-id]')?.dataset.productId;
    return productId ? { retailer: 'walmart', id: productId, type: 'walmart-id' } : null;
  }
  
  if (hostname.includes('target')) {
    const title = document.querySelector('[data-test="product-title"]')?.textContent;
    return title ? { retailer: 'target', id: title, type: 'name' } : null;
  }
  
  return null;
}

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'getProductInfo') {
    const productInfo = detectProductInfo();
    sendResponse(productInfo);
  }
  return true;
});

This script detects which retailer you’re on and extracts the appropriate product identifier. Extend the selectors for additional retailers.

Background Service Worker Logic

The service worker handles API calls and data processing:

// background.js
const PRICE_API_ENDPOINT = 'https://api.your-price-service.com/v1/search';

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'findPrices') {
    fetchPrices(request.productInfo)
      .then(prices => sendResponse({ success: true, prices }))
      .catch(error => sendResponse({ success: false, error: error.message }));
    return true;
  }
});

async function fetchPrices(productInfo) {
  const response = await fetch(PRICE_API_ENDPOINT, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${await getApiKey()}`
    },
    body: JSON.stringify({
      retailer: productInfo.retailer,
      product_id: productInfo.id,
      id_type: productInfo.type
    })
  });
  
  if (!response.ok) {
    throw new Error(`API error: ${response.status}`);
  }
  
  return response.json();
}

async function getApiKey() {
  const result = await chrome.storage.local.get(['apiKey']);
  return result.apiKey;
}

The popup provides user controls:

<!-- popup/popup.html -->
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="../styles.css">
</head>
<body>
  <div class="container">
    <h2>Price Match Finder</h2>
    <div id="product-info">Checking page...</div>
    <div id="results" class="hidden">
      <h3>Lower Prices Found:</h3>
      <ul id="price-list"></ul>
    </div>
    <div id="error" class="hidden"></div>
  </div>
  <script src="popup.js"></script>
</body>
</html>
// popup/popup.js
document.addEventListener('DOMContentLoaded', async () => {
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  
  chrome.tabs.sendMessage(tab.id, { action: 'getProductInfo' }, async (productInfo) => {
    if (!productInfo) {
      showError('No product detected on this page');
      return;
    }
    
    displayProductInfo(productInfo);
    
    chrome.runtime.sendMessage(
      { action: 'findPrices', productInfo },
      (response) => {
        if (response.success) {
          displayPrices(response.prices);
        } else {
          showError(response.error);
        }
      }
    );
  });
});

function displayProductInfo(info) {
  document.getElementById('product-info').textContent = 
    `Detected: ${info.retailer} - ${info.id}`;
}

function displayPrices(prices) {
  const list = document.getElementById('price-list');
  prices.forEach(price => {
    const li = document.createElement('li');
    li.innerHTML = `
      <a href="${price.url}" target="_blank">${price.retailer}</a>
      - $${price.price.toFixed(2)} (save $${price.savings.toFixed(2)})
    `;
    list.appendChild(li);
  });
  document.getElementById('results').classList.remove('hidden');
}

function showError(message) {
  const errorDiv = document.getElementById('error');
  errorDiv.textContent = message;
  errorDiv.classList.remove('hidden');
}

Testing Your Extension

Load your extension in Chrome through chrome://extensions/. Enable Developer mode, click “Load unpacked”, and select your extension directory. Test on various retailer pages to verify product detection works correctly.

For development, use Chrome’s DevTools on the extension popup. Right-click the extension icon and choose “Inspect popup” to open console and debug issues.

Price API Considerations

Building a comprehensive price database requires either aggregating multiple retailer APIs or using a third-party service. Options include:

For personal projects, starting with a single retailer’s API or a limited dataset helps validate your architecture before scaling.

Extension Distribution

When ready to distribute:

  1. Create a Chrome Web Store developer account
  2. Package your extension using chrome://extensions/ → Pack extension
  3. Upload through the Chrome Web Store developer dashboard
  4. Submit for review (typically 24-72 hours)

Summary

Building a price match finder demonstrates several Chrome extension concepts: content script injection, message passing between components, manifest configuration, and popup UI development. The architecture scales well—you can add more retailers, implement price alerts, or integrate with deal notification systems.

Start with a minimal viable product that detects products on two or three major retailers, then expand based on your use case and API access.

Built by theluckystrike — More at zovo.one