Building a Chrome extension for tracking Walmart prices requires understanding web scraping boundaries, Chrome extension architecture, and real-time notification systems. This guide walks through creating a functional price tracker from scratch.
Understanding the Architecture
A Walmart price tracker extension consists of three primary components:
- Background Service Worker - Handles periodic price checks and notifications
- Content Script - Injects into Walmart product pages to extract pricing data
- Popup Interface - Displays tracked items and current prices to users
Before building, recognize that Walmart’s Terms of Service restrict automated data collection. Use this knowledge responsibly—build for personal use or with explicit API partnerships.
Setting Up the Extension Structure
Create your extension directory with the following structure:
walmart-price-tracker/
├── manifest.json
├── background.js
├── content.js
├── popup.html
├── popup.js
├── popup.css
└── icons/
├── icon16.png
├── icon48.png
└── icon128.png
The manifest.json defines permissions and entry points:
{
"manifest_version": 3,
"name": "Walmart Price Tracker",
"version": "1.0.0",
"description": "Track Walmart product prices and get notified of changes",
"permissions": [
"storage",
"notifications",
"activeTab"
],
"host_permissions": [
"https://www.walmart.com/*"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"content_scripts": [{
"matches": ["https://www.walmart.com/ip/*"],
"js": ["content.js"]
}]
}
Extracting Price Data with Content Scripts
The content script runs on Walmart product pages and extracts relevant pricing information. Walmart frequently changes their DOM structure, so build resilient selectors:
// content.js
(function() {
'use strict';
function extractProductData() {
const data = {
productId: null,
title: '',
currentPrice: null,
originalPrice: null,
url: window.location.href,
timestamp: new Date().toISOString()
};
// Extract product ID from URL or page
const urlMatch = window.location.href.match(/\/ip\/[^\/]+\/(\d+)/);
if (urlMatch) {
data.productId = urlMatch[1];
}
// Extract title - Walmart uses multiple selectors
const titleSelectors = [
'[data-testid="product-title"]',
'h1[itemprop="name"]',
'.prod-ProductTitle'
];
for (const selector of titleSelectors) {
const titleEl = document.querySelector(selector);
if (titleEl) {
data.title = titleEl.textContent.trim();
break;
}
}
// Extract current price
const priceSelectors = [
'[data-testid="price-wrap"] .price-characteristic',
'[itemprop="price"]',
'.price-characteristic'
];
for (const selector of priceSelectors) {
const priceEl = document.querySelector(selector);
if (priceEl) {
data.currentPrice = parseFloat(priceEl.getAttribute('content') || priceEl.textContent);
break;
}
}
// Extract original price (if on sale)
const originalSelectors = [
'[data-testid="was-price"] .price-characteristic',
'.strike-through'
];
for (const selector of originalSelectors) {
const origEl = document.querySelector(selector);
if (origEl) {
data.originalPrice = parseFloat(origEl.getAttribute('content') || origEl.textContent);
break;
}
}
return data;
}
// Listen for messages from popup or background
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'getProductData') {
const data = extractProductData();
sendResponse(data);
}
return true;
});
})();
Managing Data Storage
Use Chrome’s storage API to persist tracked products:
// background.js
'use strict';
const STORAGE_KEY = 'tracked_products';
async function getTrackedProducts() {
const result = await chrome.storage.local.get(STORAGE_KEY);
return result[STORAGE_KEY] || [];
}
async function saveTrackedProduct(product) {
const products = await getTrackedProducts();
const existingIndex = products.findIndex(p => p.productId === product.productId);
if (existingIndex >= 0) {
products[existingIndex] = { ...products[existingIndex], ...product, lastUpdated: new Date().toISOString() };
} else {
products.push({
...product,
addedAt: new Date().toISOString(),
lastUpdated: new Date().toISOString(),
priceHistory: [{ price: product.currentPrice, date: new Date().toISOString() }]
});
}
await chrome.storage.local.set({ [STORAGE_KEY]: products });
return products;
}
async function removeTrackedProduct(productId) {
const products = await getTrackedProducts();
const filtered = products.filter(p => p.productId !== productId);
await chrome.storage.local.set({ [STORAGE_KEY]: filtered });
return filtered;
}
// Price check logic - fetches product pages and extracts current prices
async function checkPrices() {
const products = await getTrackedProducts();
const updatedProducts = [];
for (const product of products) {
try {
const response = await fetch(product.url);
const html = await response.text();
// Extract price from HTML (simplified - real implementation needs proper parsing)
const priceMatch = html.match(/"price":"?(\d+\.?\d*)"?/);
const newPrice = priceMatch ? parseFloat(priceMatch[1]) : null;
if (newPrice && newPrice !== product.currentPrice) {
const priceChange = {
price: newPrice,
date: new Date().toISOString()
};
updatedProducts.push({
...product,
currentPrice: newPrice,
priceHistory: [...product.priceHistory, priceChange],
lastUpdated: new Date().toISOString()
});
// Send notification for price changes
await sendPriceNotification(product.title, product.currentPrice, newPrice);
} else {
updatedProducts.push(product);
}
} catch (error) {
console.error(`Error checking price for ${product.productId}:`, error);
updatedProducts.push(product);
}
}
await chrome.storage.local.set({ [STORAGE_KEY]: updatedProducts });
}
async function sendPriceNotification(title, oldPrice, newPrice) {
const direction = newPrice < oldPrice ? 'dropped' : 'increased';
const change = Math.abs(oldPrice - newPrice).toFixed(2);
await chrome.notifications.create({
type: 'basic',
iconUrl: 'icons/icon128.png',
title: 'Walmart Price Alert',
message: `${title} price has ${direction} by $${change}`
});
}
// Check prices every 6 hours
setInterval(checkPrices, 6 * 60 * 60 * 1000);
Building the Popup Interface
The popup provides users with a dashboard to manage tracked products:
<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="container">
<header>
<h1>Walmart Price Tracker</h1>
<button id="currentPageBtn">Track This Product</button>
</header>
<div id="trackedProducts">
<h2>Tracked Products</h2>
<ul id="productList"></ul>
</div>
<footer>
<button id="checkNowBtn">Check Prices Now</button>
<span id="lastCheck"></span>
</footer>
</div>
<script src="popup.js"></script>
</body>
</html>
// popup.js
document.addEventListener('DOMContentLoaded', async () => {
const productList = document.getElementById('productList');
const currentPageBtn = document.getElementById('currentPageBtn');
const checkNowBtn = document.getElementById('checkNowBtn');
// Load and display tracked products
async function loadProducts() {
const result = await chrome.storage.local.get('tracked_products');
const products = result.tracked_products || [];
productList.innerHTML = '';
if (products.length === 0) {
productList.innerHTML = '<li class="empty">No products tracked yet</li>';
return;
}
for (const product of products) {
const li = document.createElement('li');
li.className = 'product-item';
li.innerHTML = `
<div class="product-info">
<a href="${product.url}" target="_blank">${product.title}</a>
<span class="price">$${product.currentPrice.toFixed(2)}</span>
</div>
<button class="remove-btn" data-id="${product.productId}">×</button>
`;
productList.appendChild(li);
}
}
// Track current Walmart product
currentPageBtn.addEventListener('click', async () => {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab.url.includes('walmart.com/ip/')) {
alert('Navigate to a Walmart product page first');
return;
}
chrome.tabs.sendMessage(tab.id, { action: 'getProductData' }, async (productData) => {
if (productData && productData.productId) {
await saveTrackedProduct(productData);
loadProducts();
currentPageBtn.textContent = 'Product Tracked!';
setTimeout(() => currentPageBtn.textContent = 'Track This Product', 2000);
}
});
});
// Remove product from tracking
productList.addEventListener('click', async (e) => {
if (e.target.classList.contains('remove-btn')) {
const productId = e.target.dataset.id;
await removeTrackedProduct(productId);
loadProducts();
}
});
// Manual price check
checkNowBtn.addEventListener('click', async () => {
checkNowBtn.textContent = 'Checking...';
await checkPrices();
checkNowBtn.textContent = 'Check Prices Now';
loadProducts();
});
loadProducts();
});
Loading and Testing Your Extension
To test your extension in Chrome:
- Navigate to
chrome://extensions/ - Enable “Developer mode” (top right toggle)
- Click “Load unpacked”
- Select your extension directory
When visiting any Walmart product page, click the extension icon to add it to your tracking list. The background service will check prices every 6 hours and notify you of changes.
Production Considerations
For a production extension, consider these enhancements:
- Implement proper error handling for network failures
- Add data encryption for stored prices
- Create a settings page for customization
- Implement rate limiting to avoid detection
- Add support for multiple retailers beyond Walmart
- Consider implementing the official Walmart API if available for your use case
Building a price tracker teaches valuable skills in Chrome extension development, web scraping ethics, and real-time notification systems. Use these principles to create useful tools while respecting platform terms of service.
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