Back to Blog
Browser ExtensionsChromeFirefoxTutorial

Building Browser Extensions with AI Assistance

Create powerful browser extensions for Chrome, Firefox, and Edge with AI guidance. From manifest to store submission.

B
Bootspring Team
Engineering
October 25, 2025
7 min read

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});
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.

Share this article

Help spread the word about Bootspring