Building a Chrome extension that aggregates discount codes from e-commerce sites addresses a real pain point for developers and power users who want to save money online. Rather than visiting multiple coupon sites or manually searching for codes, users can have a tool that does the heavy lifting automatically.
This guide walks you through building a discount code aggregator extension from scratch. You’ll learn the architecture, implementation patterns, and key considerations for creating a useful tool.
How Discount Code Aggregators Work
A discount code aggregator performs three main functions: discovery, validation, and presentation. Understanding these phases helps you build an effective extension.
Discovery involves finding coupon codes on web pages. Extensions typically inject content scripts into e-commerce checkout pages to detect available discount input fields. Some aggregators also scrape coupon databases or use APIs from deal-aggregation services.
Validation tests whether discovered codes actually work. The extension applies each code programmatically and checks the resulting discount. This requires careful handling to avoid triggering fraud detection or rate limits.
Presentation displays available codes to users in a clean interface. Most extensions show a popup when users click the extension icon, displaying validated codes ranked by discount amount.
Extension Architecture
Here’s a practical architecture for a discount code aggregator:
/discount-aggregator
├── manifest.json
├── background.js
├── content.js
├── popup/
│ ├── popup.html
│ ├── popup.js
│ └── popup.css
├── utils/
│ ├── coupon-detector.js
│ ├── code-validator.js
│ └── storage.js
└── icons/
The manifest file defines permissions and the extension structure:
{
"manifest_version": 3,
"name": "Discount Code Aggregator",
"version": "1.0.0",
"permissions": [
"activeTab",
"storage",
"scripting"
],
"host_permissions": [
"*://*.example-coupons.com/*"
],
"action": {
"default_popup": "popup/popup.html"
},
"content_scripts": [{
"matches": ["*://*/*"],
"js": ["content.js"]
}]
}
Detecting Coupon Fields
The content script runs on every page and detects coupon input fields. Here’s a robust detection pattern:
// content.js
const COUPON_SELECTORS = [
'input[name*="coupon" i]',
'input[name*="discount" i]',
'input[name*="promo" i]',
'input[id*="coupon" i]',
'input[placeholder*="coupon" i]',
'input[placeholder*="promo" i]',
'input[aria-label*="coupon" i]',
'[data-testid*="coupon" i]'
];
function detectCouponField() {
for (const selector of COUPON_SELECTORS) {
const field = document.querySelector(selector);
if (field && isVisible(field)) {
return {
field: field,
applyButton: findApplyButton(field),
form: field.closest('form')
};
}
}
return null;
}
function isVisible(element) {
const style = window.getComputedStyle(element);
return style.display !== 'none' &&
style.visibility !== 'hidden' &&
style.opacity !== '0';
}
function findApplyButton(couponField) {
const form = couponField.closest('form');
if (!form) return null;
const buttons = form.querySelectorAll('button, input[type="submit"]');
for (const button of buttons) {
const text = button.textContent?.toLowerCase() || '';
if (text.includes('apply') || text.includes('redeem')) {
return button;
}
}
return buttons[buttons.length - 1];
}
Validating Discount Codes
Validation requires simulating the checkout process without completing a purchase. The key is to listen for the result message that e-commerce platforms display:
// utils/code-validator.js
class CouponValidator {
constructor() {
this.validationTimeout = 5000;
}
async validateCode(code, checkoutInfo) {
return new Promise(async (resolve) => {
const result = await this.attemptApplyCode(code, checkoutInfo);
const timeout = setTimeout(() => {
resolve({
success: false,
error: 'Validation timeout'
});
}, this.validationTimeout);
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
const resultText = this.extractResultMessage(node);
if (resultText) {
clearTimeout(timeout);
observer.disconnect();
resolve(this.parseResult(resultText, code));
}
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
});
}
async attemptApplyCode(code, checkoutInfo) {
const couponField = detectCouponField();
if (!couponField) {
return { success: false, error: 'No coupon field found' };
}
couponField.field.value = code;
couponField.field.dispatchEvent(new Event('input', { bubbles: true }));
couponField.field.dispatchEvent(new Event('change', { bubbles: true }));
await this.sleep(300);
if (couponField.applyButton) {
couponField.applyButton.click();
}
return { success: true };
}
extractResultMessage(node) {
const text = node.textContent?.toLowerCase() || '';
const successPatterns = [
'coupon applied', 'discount applied', 'code applied',
'promo applied', 'saved', 'discount accepted'
];
const failurePatterns = [
'invalid', 'expired', 'not valid', 'does not work',
'cannot be combined', 'minimum purchase', 'code expired'
];
if (successPatterns.some(p => text.includes(p))) {
return { type: 'success', text: node.textContent };
}
if (failurePatterns.some(p => text.includes(p))) {
return { type: 'failure', text: node.textContent };
}
return null;
}
parseResult(result, code) {
const isSuccess = result.type === 'success';
const amountMatch = result.text.match(/(\$|€|£)\s*(\d+(?:\.\d{2})?)/);
return {
success: isSuccess,
code: code,
discount: amountMatch ? amountMatch[2] : null,
currency: amountMatch ? amountMatch[1] : null,
message: result.text
};
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
Managing Code Storage
Use Chrome’s storage API to persist validated codes across sessions:
// utils/storage.js
const STORAGE_KEY = 'discount_codes';
class CodeStorage {
async saveCode(domain, codeInfo) {
const stored = await this.getAllCodes();
if (!stored[domain]) {
stored[domain] = [];
}
const existingIndex = stored[domain].findIndex(
c => c.code === codeInfo.code
);
if (existingIndex >= 0) {
stored[domain][existingIndex] = { ...codeInfo, updatedAt: Date.now() };
} else {
stored[domain].push({ ...codeInfo, createdAt: Date.now() });
}
await chrome.storage.local.set({ [STORAGE_KEY]: stored });
return stored;
}
async getCodesForDomain(domain) {
const stored = await this.getAllCodes();
return stored[domain] || [];
}
async getAllCodes() {
const result = await chrome.storage.local.get(STORAGE_KEY);
return result[STORAGE_KEY] || {};
}
async clearExpiredCodes() {
const stored = await this.getAllCodes();
const now = Date.now();
const sevenDays = 7 * 24 * 60 * 60 * 1000;
for (const domain in stored) {
stored[domain] = stored[domain].filter(code => {
const age = now - (code.updatedAt || code.createdAt || 0);
return age < sevenDays;
});
}
await chrome.storage.local.set({ [STORAGE_KEY]: stored });
}
}
Handling Multiple Sources
For a more comprehensive aggregator, fetch codes from external coupon APIs:
// utils/external-sources.js
async function fetchExternalCodes(currentUrl) {
const domain = new URL(currentUrl).hostname;
const apiEndpoints = [
`https://api.coupon-aggregator.example/v1/codes?domain=${domain}`,
`https://deals.example.com/api/coupons?site=${domain}`
];
const codes = [];
for (const endpoint of apiEndpoints) {
try {
const response = await fetch(endpoint, {
headers: { 'Accept': 'application/json' }
});
if (response.ok) {
const data = await response.json();
codes.push(...data.codes.map(c => ({
code: c.promo_code,
source: 'external',
description: c.description,
expiresAt: c.expiry_date
})));
}
} catch (error) {
console.log(`Failed to fetch from ${endpoint}:`, error);
}
}
return codes;
}
Building the Popup Interface
The popup displays validated codes to users:
<!-- popup/popup.html -->
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="popup-container">
<h2>Discount Codes</h2>
<div id="domain-info"></div>
<div id="codes-list"></div>
<button id="refresh-btn">Refresh Codes</button>
</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 });
const domain = new URL(tab.url).hostname;
const storage = new CodeStorage();
const codes = await storage.getCodesForDomain(domain);
const codesList = document.getElementById('codes-list');
if (codes.length === 0) {
codesList.innerHTML = '<p class="no-codes">No codes found for this site.</p>';
} else {
codesList.innerHTML = codes
.sort((a, b) => (b.discount || 0) - (a.discount || 0))
.map(code => `
<div class="code-card">
<div class="code-value">${code.code}</div>
<div class="code-description">${code.description || ''}</div>
<div class="code-actions">
<button class="copy-btn" data-code="${code.code}">Copy</button>
</div>
</div>
`).join('');
document.querySelectorAll('.copy-btn').forEach(btn => {
btn.addEventListener('click', () => {
navigator.clipboard.writeText(btn.dataset.code);
btn.textContent = 'Copied!';
setTimeout(() => btn.textContent = 'Copy', 2000);
});
});
}
});
Ethical Considerations
When building discount code aggregators, keep these guidelines in mind:
- Rate Limiting: Implement delays between validation attempts to avoid overwhelming server resources
- Terms of Service: Some sites explicitly prohibit automated coupon testing
- User Privacy: Only store codes locally; never send user browsing data to external servers
- Transparency: Clearly communicate to users how your extension works
Conclusion
A Chrome extension discount code aggregator combines web scraping, programmatic form interaction, and local storage to help users find working coupon codes. The implementation requires careful attention to DOM detection, result parsing, and ethical operation practices.
This guide provides the foundation for building your own aggregator. You can extend it with additional features like price tracking, deal alerts, or integration with deal-sharing communities. Remember to test thoroughly across different e-commerce platforms, as each has unique checkout flows and coupon field implementations.
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