Claude Skills Guide

Building a price history Chrome extension gives you the power to track and visualize price changes across any e-commerce site. Unlike basic price trackers that only show current prices, a price history extension stores historical data, allowing users to identify trends, spot seasonal discounts, and make informed purchasing decisions.

This guide covers the technical implementation for developers and power users who want to build custom price tracking solutions.

Core Architecture

A price history extension consists of three main components:

  1. Content Script - Extracts price data from web pages
  2. Background Service Worker - Handles data storage and notifications
  3. Popup UI - Displays price history charts and configuration

The extension uses Chrome’s storage API to persist price data locally. For larger datasets, consider IndexedDB for better query performance.

Extracting Price Data

Price extraction varies significantly across e-commerce platforms. A robust implementation handles multiple price formats and selectors. Here is a content script pattern for extracting prices:

// content-script.js
class PriceExtractor {
  constructor() {
    this.selectors = {
      mainPrice: [
        '.price', 
        '[data-price]', 
        '.product-price',
        '.price-current'
      ],
      originalPrice: [
        '.price-was',
        '.price-original',
        '.strike-through'
      ],
      salePrice: [
        '.price-sale',
        '.sale-price'
      ]
    };
  }

  extract() {
    const data = {
      url: window.location.href,
      productName: this.extractProductName(),
      currentPrice: this.extractPrice(this.selectors.mainPrice),
      originalPrice: this.extractPrice(this.selectors.originalPrice),
      salePrice: this.extractPrice(this.selectors.salePrice),
      currency: this.detectCurrency(),
      timestamp: Date.now()
    };
    
    return this.validateData(data) ? data : null;
  }

  extractPrice(selectors) {
    for (const selector of selectors) {
      const element = document.querySelector(selector);
      if (element) {
        const text = element.textContent.trim();
        const price = this.parsePrice(text);
        if (price !== null) return price;
      }
    }
    return null;
  }

  parsePrice(text) {
    // Handle various price formats: $19.99, €19,99, £19.99, etc.
    const match = text.match(/[\d,]+\.?\d*/);
    if (!match) return null;
    
    let price = match[0].replace(/,/g, '');
    return parseFloat(price);
  }

  detectCurrency() {
    const currencyEl = document.querySelector('[data-currency], .currency');
    if (currencyEl) return currencyEl.dataset.currency || currencyEl.textContent;
    
    // Fallback: detect from page locale or body class
    return 'USD';
  }

  extractProductName() {
    const titleEl = document.querySelector('h1, [data-product-title]');
    return titleEl?.textContent.trim() || 
           document.title.split('|')[0].trim();
  }

  validateData(data) {
    return data.currentPrice !== null && 
           data.currentPrice > 0 &&
           data.productName.length > 0;
  }
}

Data Storage Strategy

Chrome Extensions offer multiple storage options. For price history, you need to balance storage limits with query performance:

Storage Type Capacity Best For
chrome.storage.local 5MB Small to medium datasets
chrome.storage.sync 100KB User preferences only
IndexedDB 50%+ of disk Large price histories

A practical storage implementation:

// background/priceStore.js
const DB_NAME = 'PriceHistoryDB';
const STORE_NAME = 'prices';

class PriceStore {
  constructor() {
    this.db = null;
  }

  async init() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(DB_NAME, 1);
      
      request.onerror = () => reject(request.error);
      request.onsuccess = () => {
        this.db = request.result;
        resolve();
      };
      
      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        if (!db.objectStoreNames.contains(STORE_NAME)) {
          const store = db.createObjectStore(STORE_NAME, { 
            keyPath: 'id', 
            autoIncrement: true 
          });
          store.createIndex('url', 'url', { unique: false });
          store.createIndex('timestamp', 'timestamp', { unique: false });
        }
      };
    });
  }

  async savePrice(priceData) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([STORE_NAME], 'readwrite');
      const store = transaction.objectStore(STORE_NAME);
      
      // Attach unique identifier based on URL
      priceData.urlHash = this.hashURL(priceData.url);
      priceData.id = `${priceData.urlHash}_${priceData.timestamp}`;
      
      const request = store.put(priceData);
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error);
    });
  }

  async getPriceHistory(url, options = {}) {
    const { limit = 100, days = 90 } = options;
    const cutoff = Date.now() - (days * 24 * 60 * 60 * 1000);
    
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([STORE_NAME], 'readonly');
      const store = transaction.objectStore(STORE_NAME);
      const index = store.index('url');
      
      const results = [];
      const request = index.openCursor(IDBKeyRange.only(url));
      
      request.onsuccess = (event) => {
        const cursor = event.target.result;
        if (cursor && results.length < limit) {
          if (cursor.value.timestamp >= cutoff) {
            results.push(cursor.value);
          }
          cursor.continue();
        } else {
          resolve(results.sort((a, b) => a.timestamp - b.timestamp));
        }
      };
      
      request.onerror = () => reject(request.error);
    });
  }

  hashURL(url) {
    // Simple hash for URL identification
    let hash = 0;
    for (let i = 0; i < url.length; i++) {
      const char = url.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash;
    }
    return Math.abs(hash).toString(36);
  }
}

Message Passing System

Content scripts communicate with the background script using message passing:

// content-script.js - sending price data
async function reportPrice() {
  const extractor = new PriceExtractor();
  const priceData = extractor.extract();
  
  if (priceData) {
    try {
      await chrome.runtime.sendMessage({
        action: 'savePrice',
        data: priceData
      });
      console.log('Price saved:', priceData.currentPrice);
    } catch (error) {
      console.error('Failed to save price:', error);
    }
  }
}

// Run on page load and periodically for dynamic prices
reportPrice();
setInterval(reportPrice, 60000);
// background/service-worker.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === 'savePrice') {
    handleSavePrice(message.data);
  } else if (message.action === 'getHistory') {
    handleGetHistory(message.url).then(sendResponse);
    return true; // async response
  }
});

async function handleSavePrice(priceData) {
  const store = new PriceStore();
  await store.init();
  await store.savePrice(priceData);
}

async function handleGetHistory(url) {
  const store = new PriceStore();
  await store.init();
  return await store.getPriceHistory(url);
}

Price History Visualization

The popup UI should display historical data as a chart. Using a lightweight charting library:

// popup/chart.js
function renderPriceChart(prices, containerId) {
  const ctx = document.getElementById(containerId);
  
  const labels = prices.map(p => new Date(p.timestamp).toLocaleDateString());
  const data = prices.map(p => p.currentPrice);
  
  new Chart(ctx, {
    type: 'line',
    data: {
      labels: labels,
      datasets: [{
        label: 'Price History',
        data: data,
        borderColor: '#4CAF50',
        backgroundColor: 'rgba(76, 175, 80, 0.1)',
        fill: true,
        tension: 0.3
      }]
    },
    options: {
      responsive: true,
      plugins: {
        legend: { display: false },
        tooltip: {
          callbacks: {
            label: (context) => `$${context.raw.toFixed(2)}`
          }
        }
      },
      scales: {
        y: {
          beginAtZero: false,
          title: { display: true, text: 'Price' }
        }
      }
    }
  });
}

Extension Manifest Configuration

Your manifest.json needs proper permissions:

{
  "manifest_version": 3,
  "name": "Price History Tracker",
  "version": "1.0.0",
  "permissions": [
    "storage",
    "activeTab",
    "scripting"
  ],
  "host_permissions": [
    "*://*.amazon.com/*",
    "*://*.walmart.com/*",
    "*://*.target.com/*"
  ],
  "background": {
    "service_worker": "background/service-worker.js"
  },
  "content_scripts": [{
    "matches": ["*://*/*"],
    "js": ["content-script.js"],
    "run_at": "document_idle"
  }],
  "action": {
    "default_popup": "popup/popup.html",
    "default_icon": "icons/icon.png"
  }
}

Privacy Considerations

When building price tracking extensions, respect user privacy:

Deployment and Testing

Test your extension thoroughly across different e-commerce sites. Use Chrome’s built-in testing features:

# Load unpacked extension for testing
# 1. Navigate to chrome://extensions
# 2. Enable "Developer mode"
# 3. Click "Load unpacked"
# 4. Select your extension directory

Monitor console logs in both the popup and service worker for debugging. Use Chrome’s Storage Inspector to view persisted price data during development.

Building a price history extension requires handling diverse price formats, managing storage efficiently, and creating useful visualizations. The implementation above provides a solid foundation that you can customize for specific retailers or add features like price drop alerts and shopping lists.

Built by theluckystrike — More at zovo.one