Browser extensions extend what browsers can do. From productivity tools to developer utilities, extensions add powerful capabilities. AI can help you navigate the complex APIs, manifest configurations, and cross-browser compatibility challenges.
Extension Architecture#
Core Components#
my-extension/
├── manifest.json # Extension configuration
├── background/
│ └── service-worker.js # Background processing
├── content/
│ └── content.js # Page injection scripts
├── popup/
│ ├── popup.html # Popup UI
│ ├── popup.js # Popup logic
│ └── popup.css # Popup styles
├── options/
│ ├── options.html # Settings page
│ └── options.js # Settings logic
└── icons/
├── icon-16.png
├── icon-48.png
└── icon-128.png
Manifest V3 Configuration#
1{
2 "manifest_version": 3,
3 "name": "My Awesome Extension",
4 "version": "1.0.0",
5 "description": "A helpful browser extension",
6
7 "permissions": [
8 "storage",
9 "activeTab",
10 "scripting"
11 ],
12
13 "host_permissions": [
14 "https://*.example.com/*"
15 ],
16
17 "background": {
18 "service_worker": "background/service-worker.js"
19 },
20
21 "action": {
22 "default_popup": "popup/popup.html",
23 "default_icon": {
24 "16": "icons/icon-16.png",
25 "48": "icons/icon-48.png",
26 "128": "icons/icon-128.png"
27 }
28 },
29
30 "content_scripts": [
31 {
32 "matches": ["https://*.example.com/*"],
33 "js": ["content/content.js"],
34 "css": ["content/content.css"]
35 }
36 ],
37
38 "options_page": "options/options.html",
39
40 "icons": {
41 "16": "icons/icon-16.png",
42 "48": "icons/icon-48.png",
43 "128": "icons/icon-128.png"
44 }
45}Building Your First Extension#
AI-Assisted Setup#
Create a browser extension that:
- Highlights all links on a page
- Shows link count in the popup
- Allows toggling highlight on/off
- Remembers preference per site
Target: Chrome (Manifest V3)
Include:
- Complete manifest.json
- Background service worker
- Content script
- Popup with toggle button
- Storage for preferences
Background Service Worker#
1// background/service-worker.js
2
3// Handle extension install/update
4chrome.runtime.onInstalled.addListener((details) => {
5 if (details.reason === 'install') {
6 // Set default settings
7 chrome.storage.sync.set({
8 enabled: true,
9 highlightColor: '#ffff00'
10 });
11 }
12});
13
14// Listen for messages from content scripts or popup
15chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
16 switch (message.type) {
17 case 'GET_SETTINGS':
18 chrome.storage.sync.get(['enabled', 'highlightColor'], sendResponse);
19 return true; // Keep channel open for async response
20
21 case 'TOGGLE_HIGHLIGHT':
22 toggleHighlight(sender.tab.id, message.enabled);
23 break;
24
25 case 'GET_LINK_COUNT':
26 // Query the content script
27 chrome.tabs.sendMessage(sender.tab?.id || message.tabId, {
28 type: 'COUNT_LINKS'
29 }, sendResponse);
30 return true;
31 }
32});
33
34async function toggleHighlight(tabId, enabled) {
35 await chrome.tabs.sendMessage(tabId, {
36 type: 'SET_HIGHLIGHT',
37 enabled
38 });
39}Content Script#
1// content/content.js
2
3let isHighlighted = false;
4
5// Apply highlight to all links
6function highlightLinks(color) {
7 const links = document.querySelectorAll('a');
8 links.forEach(link => {
9 link.dataset.originalBg = link.style.backgroundColor;
10 link.style.backgroundColor = color;
11 });
12 isHighlighted = true;
13}
14
15// Remove highlight from all links
16function removeHighlight() {
17 const links = document.querySelectorAll('a');
18 links.forEach(link => {
19 link.style.backgroundColor = link.dataset.originalBg || '';
20 });
21 isHighlighted = false;
22}
23
24// Count links on page
25function countLinks() {
26 return document.querySelectorAll('a').length;
27}
28
29// Listen for messages from background/popup
30chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
31 switch (message.type) {
32 case 'SET_HIGHLIGHT':
33 if (message.enabled) {
34 chrome.storage.sync.get(['highlightColor'], (settings) => {
35 highlightLinks(settings.highlightColor || '#ffff00');
36 });
37 } else {
38 removeHighlight();
39 }
40 break;
41
42 case 'COUNT_LINKS':
43 sendResponse({ count: countLinks() });
44 break;
45
46 case 'GET_STATUS':
47 sendResponse({ isHighlighted });
48 break;
49 }
50});
51
52// Initialize on load
53chrome.storage.sync.get(['enabled', 'highlightColor'], (settings) => {
54 if (settings.enabled) {
55 highlightLinks(settings.highlightColor || '#ffff00');
56 }
57});Popup Interface#
1<!-- popup/popup.html -->
2<!DOCTYPE html>
3<html>
4<head>
5 <link rel="stylesheet" href="popup.css">
6</head>
7<body>
8 <div class="container">
9 <h1>Link Highlighter</h1>
10
11 <div class="stat">
12 <span class="label">Links on page:</span>
13 <span id="linkCount">-</span>
14 </div>
15
16 <div class="toggle-container">
17 <label class="switch">
18 <input type="checkbox" id="enableToggle">
19 <span class="slider"></span>
20 </label>
21 <span>Enable highlighting</span>
22 </div>
23
24 <div class="color-picker">
25 <label>Highlight color:</label>
26 <input type="color" id="colorPicker" value="#ffff00">
27 </div>
28 </div>
29 <script src="popup.js"></script>
30</body>
31</html>1// popup/popup.js
2
3document.addEventListener('DOMContentLoaded', async () => {
4 const toggle = document.getElementById('enableToggle');
5 const colorPicker = document.getElementById('colorPicker');
6 const linkCount = document.getElementById('linkCount');
7
8 // Get current tab
9 const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
10
11 // Load current settings
12 const settings = await chrome.storage.sync.get(['enabled', 'highlightColor']);
13 toggle.checked = settings.enabled ?? true;
14 colorPicker.value = settings.highlightColor ?? '#ffff00';
15
16 // Get link count from content script
17 try {
18 const response = await chrome.tabs.sendMessage(tab.id, { type: 'COUNT_LINKS' });
19 linkCount.textContent = response.count;
20 } catch (e) {
21 linkCount.textContent = 'N/A';
22 }
23
24 // Handle toggle change
25 toggle.addEventListener('change', async () => {
26 const enabled = toggle.checked;
27 await chrome.storage.sync.set({ enabled });
28 await chrome.tabs.sendMessage(tab.id, { type: 'SET_HIGHLIGHT', enabled });
29 });
30
31 // Handle color change
32 colorPicker.addEventListener('change', async () => {
33 const highlightColor = colorPicker.value;
34 await chrome.storage.sync.set({ highlightColor });
35
36 if (toggle.checked) {
37 // Re-apply with new color
38 await chrome.tabs.sendMessage(tab.id, { type: 'SET_HIGHLIGHT', enabled: true });
39 }
40 });
41});Advanced Features#
Context Menus#
1// Create context menu on install
2chrome.runtime.onInstalled.addListener(() => {
3 chrome.contextMenus.create({
4 id: 'saveLink',
5 title: 'Save this link',
6 contexts: ['link']
7 });
8
9 chrome.contextMenus.create({
10 id: 'highlightSelection',
11 title: 'Highlight selection',
12 contexts: ['selection']
13 });
14});
15
16// Handle context menu clicks
17chrome.contextMenus.onClicked.addListener((info, tab) => {
18 switch (info.menuItemId) {
19 case 'saveLink':
20 saveLink(info.linkUrl);
21 break;
22 case 'highlightSelection':
23 chrome.tabs.sendMessage(tab.id, {
24 type: 'HIGHLIGHT_SELECTION',
25 text: info.selectionText
26 });
27 break;
28 }
29});Cross-Tab Communication#
1// Broadcast to all tabs
2async function broadcastToAllTabs(message) {
3 const tabs = await chrome.tabs.query({});
4 for (const tab of tabs) {
5 try {
6 await chrome.tabs.sendMessage(tab.id, message);
7 } catch (e) {
8 // Tab might not have content script
9 }
10 }
11}
12
13// Sync state across tabs
14chrome.storage.onChanged.addListener((changes, area) => {
15 if (area === 'sync' && changes.enabled) {
16 broadcastToAllTabs({
17 type: 'SET_HIGHLIGHT',
18 enabled: changes.enabled.newValue
19 });
20 }
21});Network Request Interception#
1// manifest.json - add declarativeNetRequest permission
2{
3 "permissions": ["declarativeNetRequest"],
4 "declarative_net_request": {
5 "rule_resources": [{
6 "id": "ruleset_1",
7 "enabled": true,
8 "path": "rules.json"
9 }]
10 }
11}
12
13// rules.json - block tracking scripts
14[
15 {
16 "id": 1,
17 "priority": 1,
18 "action": { "type": "block" },
19 "condition": {
20 "urlFilter": "*tracking*",
21 "resourceTypes": ["script"]
22 }
23 }
24]Cross-Browser Development#
Browser API Compatibility#
1// Use webextension-polyfill for cross-browser compatibility
2// npm install webextension-polyfill
3
4import browser from 'webextension-polyfill';
5
6// Now use promise-based API everywhere
7const settings = await browser.storage.sync.get(['enabled']);
8await browser.tabs.sendMessage(tabId, message);Manifest Differences#
Ask AI:
"Create manifest files for Chrome, Firefox, and Edge for this extension"
AI generates:
- manifest.json (Chrome/Edge - MV3)
- manifest.firefox.json (Firefox - MV2/MV3)
- Build script to swap manifests
Security Best Practices#
Content Security Policy#
{
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'"
}
}Minimal Permissions#
Ask AI:
"Review this manifest for minimal permissions"
AI checks:
- Remove unused permissions
- Use activeTab instead of broad host permissions
- Use optional_permissions for rare features
- Justify each permission in documentation
Data Handling#
1// Never store sensitive data in content scripts
2// Use background service worker + encrypted storage
3
4// Bad: Content script with sensitive data
5localStorage.setItem('apiKey', key); // Accessible to page
6
7// Good: Background script with chrome.storage
8chrome.storage.local.set({ apiKey: encryptedKey });Testing Extensions#
Manual Testing#
1. Load unpacked extension in chrome://extensions
2. Enable Developer Mode
3. Click "Load unpacked"
4. Select extension directory
5. Test all features
6. Check console for errors
Automated Testing#
1// Using Playwright for extension testing
2import { test, chromium } from '@playwright/test';
3
4test('extension popup works', async () => {
5 const pathToExtension = './dist';
6 const context = await chromium.launchPersistentContext('', {
7 headless: false,
8 args: [
9 `--disable-extensions-except=${pathToExtension}`,
10 `--load-extension=${pathToExtension}`
11 ]
12 });
13
14 // Get extension ID
15 const backgroundPages = context.backgroundPages();
16 const extensionId = backgroundPages[0].url().split('/')[2];
17
18 // Test popup
19 const popup = await context.newPage();
20 await popup.goto(`chrome-extension://${extensionId}/popup/popup.html`);
21 await popup.click('#enableToggle');
22 // Assert expected behavior
23});Publishing#
Chrome Web Store#
Ask AI:
"Prepare this extension for Chrome Web Store submission"
AI provides:
1. Store listing requirements
2. Screenshot specifications (1280x800)
3. Privacy policy template
4. Description optimization
5. Category selection
6. Review process expectations
Firefox Add-ons#
Differences from Chrome:
- Use web-ext tool for packaging
- Sign extension for self-distribution
- Review process differs
- Manifest V2 still supported
Common Patterns#
Page Action (Conditional Icon)#
1// Show icon only on specific sites
2chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
3 if (tab.url?.includes('github.com')) {
4 chrome.action.enable(tabId);
5 } else {
6 chrome.action.disable(tabId);
7 }
8});Badge Updates#
1// Update badge with count
2async function updateBadge(tabId) {
3 const response = await chrome.tabs.sendMessage(tabId, { type: 'COUNT_ITEMS' });
4 chrome.action.setBadgeText({
5 text: String(response.count),
6 tabId
7 });
8 chrome.action.setBadgeBackgroundColor({
9 color: '#4CAF50',
10 tabId
11 });
12}Side Panel (Chrome 114+)#
1{
2 "side_panel": {
3 "default_path": "sidepanel/index.html"
4 },
5 "permissions": ["sidePanel"]
6}Conclusion#
Browser extensions are powerful tools that can significantly enhance user experience. With AI assistance, you can navigate the complex APIs, handle cross-browser compatibility, and implement features that would otherwise require extensive documentation diving.
Start simple: pick one feature, implement it for Chrome, then expand. Use AI to generate boilerplate, understand APIs, and troubleshoot issues. The result is professional extensions that solve real problems for users.