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:
- Content Script - Extracts price data from web pages
- Background Service Worker - Handles data storage and notifications
- 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:
- Store only necessary price data, not personal information
- Provide clear data export and deletion options
- Avoid sending browsing data to external servers without consent
- Use HTTPS for any network requests
- Consider adding a “do not track” mode
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.
Related Reading
- Claude Code for Beginners: Complete Getting Started Guide
- Best Claude Skills for Developers in 2026
- Claude Skills Guides Hub
Built by theluckystrike — More at zovo.one