Building a Price Comparison Chrome Extension: A Developer’s Guide
Price tracking and comparison tools have become essential for online shoppers and developers building e-commerce platforms. Chrome extensions offer a powerful way to automate price comparisons directly in the browser, providing real-time alerts and historical data without requiring users to visit separate websites.
This guide walks through building a price comparison Chrome extension using modern APIs and best practices.
Understanding the Architecture
A price comparison extension typically consists of three main components:
- Content scripts that run on e-commerce pages and extract product information
- Background scripts that handle data storage, API calls, and cross-tab coordination
- Popup UI for quick access to tracked products and settings
With Chrome’s Manifest V3, background scripts have shifted to service workers, which introduces some architectural considerations around persistence and state management.
Project Setup
Create your extension directory structure:
price-tracker/
├── manifest.json
├── background.js
├── content.js
├── popup/
│ ├── popup.html
│ └── popup.js
├── styles.css
└── icons/
├── icon16.png
├── icon48.png
└── icon128.png
Manifest Configuration
The manifest.json defines your extension’s capabilities:
{
"manifest_version": 3,
"name": "Price Tracker",
"version": "1.0",
"description": "Track and compare prices across e-commerce sites",
"permissions": [
"storage",
"activeTab",
"scripting"
],
"host_permissions": [
"https://*.amazon.com/*",
"https://*.ebay.com/*",
"https://*.walmart.com/*"
],
"background": {
"service_worker": "background.js"
},
"content_scripts": [{
"matches": ["https://*.amazon.com/*", "https://*.ebay.com/*"],
"js": ["content.js"]
}],
"action": {
"default_popup": "popup/popup.html",
"default_icon": {
"48": "icons/icon48.png"
}
}
}
Note the host_permissions array. In Manifest V3, you must explicitly declare which domains your extension can access. This is crucial for price comparison extensions that need to read product pages from multiple retailers.
Extracting Product Data with Content Scripts
Content scripts run in the context of web pages, giving you access to the DOM. Here’s how to extract product information from a typical e-commerce page:
// content.js
function extractProductData() {
const selectors = {
title: '[data-feature-name="title"], .product-title, h1[itemprop="name"]',
price: '[data-feature-name="priceblock_ourprice"], .price-current, [itemprop="price"]',
currency: '[itemprop="priceCurrency"]',
availability: '[data-feature-name="availability"], .availability-status'
};
const getText = (selector) => {
const el = document.querySelector(selector);
return el ? el.textContent.trim() : null;
};
const getAttribute = (selector, attr) => {
const el = document.querySelector(selector);
return el ? el.getAttribute(attr) : null;
};
return {
url: window.location.href,
title: getText(selectors.title),
price: getText(selectors.price),
currency: getAttribute(selectors.currency, 'content') || 'USD',
availability: getText(selectors.availability),
timestamp: new Date().toISOString()
};
}
// Listen for messages from popup or background
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'getProductData') {
const data = extractProductData();
sendResponse(data);
}
});
This script handles multiple selectors since different retailers use varying class names and data attributes.
Background Service Worker
The background service worker manages the extension’s core logic:
// background.js
const PRICE_HISTORY_KEY = 'price_history';
// Store price data
async function storePriceData(productUrl, productData) {
const result = await chrome.storage.local.get(PRICE_HISTORY_KEY);
const history = result[PRICE_HISTORY_KEY] || {};
if (!history[productUrl]) {
history[productUrl] = {
currentPrice: productData.price,
lowestPrice: productData.price,
highestPrice: productData.price,
prices: [],
title: productData.title,
lastUpdated: productData.timestamp
};
} else {
const product = history[productUrl];
product.prices.push({
price: productData.price,
timestamp: productData.timestamp
});
product.currentPrice = productData.price;
product.lowestPrice = Math.min(product.lowestPrice, parsePrice(productData.price));
product.highestPrice = Math.max(product.highestPrice, parsePrice(productData.price));
product.lastUpdated = productData.timestamp;
}
await chrome.storage.local.set({ [PRICE_HISTORY_KEY]: history });
}
function parsePrice(priceString) {
const numericMatch = priceString.match(/[\d,]+\.?\d*/);
return numericMatch ? parseFloat(numericMatch[0].replace(',', '')) : 0;
}
// Handle messages from content scripts and popup
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'saveProduct') {
storePriceData(request.url, request.productData)
.then(() => sendResponse({ success: true }))
.catch(err => sendResponse({ success: false, error: err.message }));
return true;
}
if (request.action === 'getHistory') {
chrome.storage.local.get(PRICE_HISTORY_KEY)
.then(result => sendResponse(result[PRICE_HISTORY_KEY] || {}))
.catch(err => sendResponse({ error: err.message }));
return true;
}
});
Popup Interface
The popup provides quick access to tracked products:
<!-- popup/popup.html -->
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="../styles.css">
</head>
<body>
<div class="popup-container">
<h2>Price Tracker</h2>
<div id="tracked-products"></div>
<button id="track-current">Track This Product</button>
</div>
<script src="popup.js"></script>
</body>
</html>
// popup/popup.js
document.addEventListener('DOMContentLoaded', () => {
loadTrackedProducts();
document.getElementById('track-current').addEventListener('click', async () => {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
chrome.tabs.sendMessage(tab.id, { action: 'getProductData' }, async (productData) => {
if (productData && productData.price) {
await chrome.runtime.sendMessage({
action: 'saveProduct',
url: productData.url,
productData: productData
});
loadTrackedProducts();
}
});
});
});
async function loadTrackedProducts() {
const result = await chrome.runtime.sendMessage({ action: 'getHistory' });
const container = document.getElementById('tracked-products');
if (!result || Object.keys(result).length === 0) {
container.innerHTML = '<p>No products tracked yet</p>';
return;
}
container.innerHTML = Object.entries(result).map(([url, data]) => `
<div class="product-card">
<h3>${data.title || 'Unknown Product'}</h3>
<p class="current-price">Current: ${data.currentPrice}</p>
<p class="price-range">
Low: ${data.lowestPrice} | High: ${data.highestPrice}
</p>
</div>
`).join('');
}
Advanced Features
Once you have the basics working, consider adding:
Price Drop Notifications: Use the Chrome Notifications API to alert users when prices fall below their target:
async function checkPriceAlert(productUrl, targetPrice) {
const history = await chrome.storage.local.get(PRICE_HISTORY_KEY);
const product = history[PRICE_HISTORY_KEY][productUrl];
if (parsePrice(product.currentPrice) <= targetPrice) {
chrome.notifications.create({
type: 'basic',
iconUrl: 'icons/icon48.png',
title: 'Price Drop Alert!',
message: `${product.title} is now ${product.currentPrice}`
});
}
}
Cross-Platform Sync: Implement cloud storage to sync price history across devices using your own backend or services like Firebase.
Analytics Dashboard: Create a dedicated extension page showing price trends with charts using a library like Chart.js.
Best Practices
When building production-ready price comparison extensions:
- Respect rate limits: Implement throttling when fetching page data to avoid triggering anti-bot measures
- Handle dynamic content: Many e-commerce sites use JavaScript to render prices—consider using MutationObserver or waiting for specific elements
- Clean your selectors: Retailers frequently change their page structure—build in flexibility with fallback selectors
- Storage management: Chrome storage has quotas—implement cleanup of old price history entries
Building a price comparison extension combines DOM manipulation, Chrome APIs, and data management into a practical project that demonstrates real-world extension development patterns.
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