Chrome Extension Discount Code (2026)
Building a Chrome extension that aggregates discount codes from e-commerce sites addresses a real problem 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 solid 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.
Step-by-Step: Auto-Apply Coupon Architecture
- Detect checkout pages: match URLs against a list of known checkout patterns (
/checkout,/cart,/order) usingchrome.declarativeNetRequestor a content scriptmatchesfilter. - Identify the coupon input field: use a ranked list of selectors.
input[name*="coupon"],input[placeholder*="promo"],input[id*="discount"]. stopping at the first match. - Fetch available codes: query your codes database (stored in
chrome.storage.local) filtered by the current domain. Sort codes by historical success rate descending. - Try codes sequentially: inject each code into the input, click the apply button, wait 1-2 seconds for the page to respond, and check whether a discount appeared in the order total.
- Record outcomes: store each attempt as
{ domain, code, success, discountAmount, testedAt }to improve future ranking. - Show results in a popup badge: update the extension action badge with the number of successful codes found. A green badge with “2” tells the user two codes worked without requiring them to open the popup.
Advanced: Price Drop Reactivation
Some discount codes expire but later get reactivated by retailers for seasonal sales. Add a background job that re-tests expired codes monthly:
chrome.alarms.create('retestExpiredCodes', { periodInMinutes: 43200 }); // 30 days
chrome.alarms.onAlarm.addListener(async (alarm) => {
if (alarm.name === 'retestExpiredCodes') {
const { codes } = await chrome.storage.local.get('codes');
const expired = codes.filter(c => !c.active && c.lastTested < Date.now() - 30 * 86400000);
// Queue them for testing on the next checkout page visit
await chrome.storage.local.set({ pendingRetest: expired.map(c => c.code) });
}
});
Comparison with Existing Coupon Extensions
| Tool | Auto-apply | Code sources | Privacy | Cost |
|---|---|---|---|---|
| This extension | Yes (build it) | Your curated DB | Local only | Free |
| Honey (PayPal) | Yes | Crowdsourced | PayPal collects data | Free |
| Capital One Shopping | Yes | Proprietary | Account required | Free |
| CouponFollow | Manual paste | Aggregated | Cookies tracked | Free |
| RetailMeNot extension | Manual | Aggregated | Account optional | Free |
The custom extension advantage is data privacy: Honey and Capital One Shopping send your browsing data to their servers. A self-hosted extension stores everything locally and only fetches codes from sources you control.
Troubleshooting
Apply button not being clicked: Many checkout pages use custom React or Vue components whose click handlers are not triggered by a synthetic element.click(). Use element.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })) to simulate a realistic click event that bubbles through the component tree.
Code field validation rejecting programmatic input: Some sites validate that the input value was typed character by character (tracking input events). Simulate typing by calling element.dispatchEvent(new InputEvent('input', { inputType: 'insertText', data: char, bubbles: true })) for each character of the code.
False positives. code “applied” but no discount shown: Parse the order summary after each attempt and compare the total before and after. If the total is unchanged, mark the code as failed even if the page showed a success message (some sites accept invalid codes silently).
Try it: Paste your error into our Error Diagnostic for an instant fix.
Related Reading
- Chrome Extension Military Discount Finder: A Developer Guide
- Advanced Claude Skills with Tool Use and Function Calling
- Agent Handoff Strategies for Long Running Tasks Guide
Built by theluckystrike. More at zovo.one
Know your costs → Use our Claude Code Cost Calculator to estimate your monthly spend.