Browser Extension as Product Companion — Chat Prompts
If your B2B SaaS workflow happens partly in OTHER applications (web, email, social, customer's CRM, customer's project tool), at some point a customer asks: "Can I do X without leaving the page?" Browser extensions are the answer. 1Password autofills passwords across the web. Grammarly checks grammar on every input. Loom records videos from any tab. Notion Web Clipper saves articles. The list of B2B SaaS with a browser extension grew dramatically 2022-2026 because the extension is sticky (always-on), proximate (where work happens), and a moat (most competitors don't ship one).
The naive shape: "We'll build a Chrome extension." Six months later, a half-finished extension with bugs in Firefox, broken in Safari, rejected by Chrome Web Store, with manifest v3 migration issues. The right shape: a real plan for cross-browser support (Chrome / Edge / Firefox / Safari), manifest v3 from day one, message-passing architecture, content-script discipline, store submission process, automated update flow, and the safety/security posture browser extensions require.
This chat walks through implementing a real browser extension companion to your SaaS: scope decisions, architecture (background / content / popup / options), cross-browser strategy, store submission, update mechanics, and operational realities.
What you're building
- Browser extension built on manifest v3
- Cross-browser support (Chrome / Edge / Firefox / Safari)
- Content scripts (interact with pages users visit)
- Background service worker (long-running logic; auth)
- Popup / sidebar UI
- Options page
- Auth / SSO with your main app
- Cross-tab + cross-device state sync
- Update mechanism
- Store submission + review compliance
- Telemetry + crash reporting
1. Decide what your extension does
Help me decide what shape of extension to build.
Three increasingly-deep shapes:
LEVEL 1: SIMPLE COMPANION (the simplest start)
- Read-only or single-action utility
- Examples: bookmark current tab; quick note to your app
- Pros: ships in 2-4 weeks
- Cons: limited utility; user might not install
LEVEL 2: ACTIVE COMPANION (passive observer)
- Reads page content; surfaces relevant info from your app
- Examples: Grammarly (inline grammar check); Honey (deal lookup); LinkedIn-finder (find someone's email)
- Pros: high-utility; sticky
- Cons: 8-16 weeks; permission concerns; performance
LEVEL 3: DEEP INTEGRATION
- Modifies pages; injects UI; takes actions on user's behalf
- Examples: 1Password (autofill); Loom (record from any tab); Notion Web Clipper
- Pros: highest utility; competitive moat
- Cons: 16-32 weeks; manifest v3 limitations; user trust
LEVEL 4: BROWSER-AS-CANVAS
- Extension IS a primary product surface
- Examples: Arc browser (different category); side-panel apps
- Pros: deep integration
- Cons: Chrome/Edge specific; very specialized
DEFAULT FOR MOST B2B SaaS:
- Year 1-2: NO extension (focus on core product)
- Year 2+: Level 1-2 if customers asking
- Year 3+: Level 3 if part of competitive moat
Don't pre-build extensions speculatively. Customer pull required.
Output: scope decision + boundary.
Output: scope statement.
2. Manifest V3 architecture
As of 2026, Chrome / Edge / Firefox have all migrated to Manifest V3. Build manifest v3 from day one.
Key MV3 changes (from MV2):
- Background scripts → service workers (no persistent background)
- Remote code disallowed
- declarativeNetRequest replaces webRequest blocking
- Cross-origin restrictions tighter
Manifest.json structure:
{
"manifest_version": 3,
"name": "YourApp Extension",
"version": "1.0.0",
"description": "Brief description",
"permissions": [
"storage",
"activeTab",
"tabs"
],
"host_permissions": [
"https://*.yourapp.com/*",
"https://*.example.com/*"
],
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["https://*.yourapp.com/*"],
"js": ["content.js"],
"css": ["content.css"]
}
],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"options_page": "options.html",
"icons": { ... },
"web_accessible_resources": [...]
}
Key components:
A. SERVICE WORKER (background.js)
- Long-running logic
- Auth refresh tokens
- API calls to your backend
- Message routing
- Note: SW can be terminated by browser; persist state in chrome.storage
B. CONTENT SCRIPT (content.js)
- Runs in page context (DOM access)
- Sandboxed (separate from page's JS)
- Can inject UI; modify DOM
- Communicates with SW via chrome.runtime.sendMessage
C. POPUP (popup.html / popup.js)
- Click extension icon → popup
- Most common UI surface
- Limited size; ephemeral (closes on click-away)
D. OPTIONS PAGE
- Extension settings
- Persistent; full page
E. SIDE PANEL (Chrome 114+)
- Persistent sidebar UI
- Newer; Chrome / Edge
Architecture pattern:
User clicks icon → Popup opens → Communicates with SW → SW calls your API → Returns data → Popup displays
User on website → Content script injected → Detects page state → Communicates with SW → SW calls API → Returns → Content script renders UI
Implement:
1. manifest.json with required permissions
2. Service worker setup
3. Content script for target sites
4. Popup UI (React / Vanilla JS / your framework)
5. Options page
6. Message-passing patterns
Output: extension architecture.
3. Cross-browser strategy
Chrome dominates (~70% market share). But Edge, Firefox, Safari matter.
Shared codebase approach:
- Manifest v3 mostly compatible across Chrome / Edge / Firefox
- Safari has its own quirks (uses different webkit-based browser API)
- Most code shared; conditionals per browser API differences
Tooling:
WebExtension Polyfill
- Mozilla's polyfill: lets you use `browser.*` (promise-based) in Chrome
- npm install webextension-polyfill
- Cross-browser code: import { browser } from 'webextension-polyfill'
Build tools:
- Webpack / Vite / Rollup
- Plasmo (modern extension framework)
- WXT (newer; opinionated)
- Build per-browser variants from same codebase
Per-browser build outputs:
- Chrome: dist/chrome (manifest v3)
- Edge: dist/edge (similar to Chrome)
- Firefox: dist/firefox (slight manifest differences)
- Safari: dist/safari (Xcode wrapper required)
Differences to handle:
Chrome / Edge:
- Mostly identical
- Manifest v3 stable
- Service worker model
Firefox:
- Manifest v3 supported (since 2024)
- Background service worker BUT also supports event pages (less restrictive)
- Some differences in action vs browser_action
Safari:
- Requires Xcode wrapper (Safari Web Extension)
- Different APIs in some cases
- App Store distribution
- Different review process
Strategy:
- v1: Chrome + Edge (90% of market; same code)
- v2: Firefox (5% of market; small additions)
- v3: Safari (4% of market; Xcode wrapper)
Implement:
1. Single codebase with browser polyfill
2. Build pipeline producing per-browser dist
3. Per-browser manifest variations
4. Test on each browser before release
Output: cross-browser maintainable codebase.
4. Authentication + SSO with main app
Critical: extension auth integration with your main SaaS.
Patterns:
PATTERN A: SHARED COOKIE / SESSION
- User logs into yourapp.com in browser
- Extension reads session cookie
- Extension uses session for API calls
- Pros: zero-friction auth (already logged in)
- Cons: requires user to be logged in to web; cookies cross-domain restrictions
PATTERN B: OAUTH FLOW
- Extension opens new tab → OAuth flow → redirects back to extension callback
- Extension stores token in chrome.storage
- Token used for API calls
- Pros: works even when not logged into web
- Cons: more friction first-time
PATTERN C: API KEY (DEV TOOLS)
- User generates API key in app settings
- Pastes into extension options
- Pros: simple
- Cons: friction; not for consumer SaaS
DEFAULT 2026: Pattern A (cookie-based) with Pattern B fallback.
Implementation:
Cookie-based:
// Background service worker
async function getAuthToken() {
const cookies = await chrome.cookies.getAll({ domain: 'yourapp.com', name: 'session' })
const sessionCookie = cookies.find(c => c.name === 'session')
if (!sessionCookie) {
return null // user not logged in
}
// Use cookie value as token; or exchange for API token
return sessionCookie.value
}
OAuth flow:
const AUTH_URL = 'https://yourapp.com/oauth/authorize?client_id=...&redirect_uri=...'
const TOKEN_URL = 'https://yourapp.com/oauth/token'
async function startAuth() {
const redirectUri = chrome.identity.getRedirectURL('callback') // chromiumapp.org
const url = `${AUTH_URL}&redirect_uri=${encodeURIComponent(redirectUri)}`
const result = await chrome.identity.launchWebAuthFlow({
url,
interactive: true,
})
const code = new URL(result).searchParams.get('code')
const tokenResp = await fetch(TOKEN_URL, {
method: 'POST',
body: JSON.stringify({ code, redirect_uri: redirectUri }),
})
const { access_token, refresh_token } = await tokenResp.json()
await chrome.storage.local.set({ access_token, refresh_token })
}
State across tabs:
- chrome.storage.local for persistent
- Sync state via storage events
- Service worker mediates state changes
Logout:
- Clear chrome.storage
- Clear cookies if applicable
- Notify all tabs / content scripts
Implement:
1. Auth flow (cookie-based primary; OAuth fallback)
2. Token refresh logic
3. Cross-tab state sync
4. Logout flow
5. UI states (logged-in vs logged-out)
Output: auth that works without making customers re-login.
5. Permissions discipline
Extensions request permissions at install. Users see them. Excessive permissions = lower install rate + store rejection.
Permission categories:
REQUIRED PERMISSIONS:
- storage (always)
- activeTab (current tab only; preferred over tabs)
OPTIONAL (request when needed):
- tabs (all tabs; broader)
- cookies (specific domains)
- notifications
- contextMenus
- alarms
HOST PERMISSIONS:
- "https://*.yourapp.com/*" (your domain; required)
- "https://*.example.com/*" (specific 3rd party)
- "<all_urls>" (everything; AVOID; users distrust)
Best practices:
A. Request minimum
- activeTab > tabs (only current tab; user clicks extension)
- Specific hosts > <all_urls>
- Optional permissions requested when feature triggered
B. Optional permissions (request at runtime)
if (!await chrome.permissions.contains({ origins: ["https://example.com/*"] })) {
await chrome.permissions.request({ origins: ["https://example.com/*"] })
}
Justify each permission in store listing.
C. Avoid permissions you don't need
- "history" — almost never needed
- "downloads" — only if downloading
- "browsingData" — rarely needed
- "geolocation" — rarely needed for B2B
Common Chrome Web Store rejections:
- "Excessive permissions"
- "Not all permissions listed are used"
- "Host permissions too broad"
D. Privacy posture
- Privacy policy required (URL in manifest)
- Data handling clear
- Don't ship "spy" extensions (track every page; sell data)
Implement:
1. Audit permissions list; remove unused
2. Use activeTab + optional permissions where possible
3. Privacy policy publicly hosted
4. Document each permission's purpose
Output: minimal-permissions extension that passes review.
6. Content script patterns
Content scripts run in page context. They're how you "augment" other websites.
Key patterns:
A. DETECT PAGE STATE
- URL pattern (e.g., LinkedIn profile page)
- DOM elements (e.g., "Compose" button on Gmail)
- Page metadata (Open Graph; structured data)
B. INJECT UI (overlay, sidebar, inline)
- Floating button
- Sidebar / panel
- Inline annotations on page elements
- Match site's visual style (or distinct extension style)
C. MODIFY PAGE (carefully)
- Don't break the host site
- Use Shadow DOM to isolate styles
- Don't override user-visible elements
D. COMMUNICATE WITH SERVICE WORKER
- chrome.runtime.sendMessage / onMessage
- For API calls (CORS often easier from SW)
- For persistence (chrome.storage)
Example: LinkedIn email finder
1. Content script detects: location.pathname starts with '/in/'
2. Extracts profile data from DOM
3. Sends to service worker
4. SW calls your API: "find email for this profile"
5. Returns email
6. Content script injects email next to profile
Code:
// content.js
function detectProfile() {
if (location.pathname.startsWith('/in/')) {
const profile = {
name: document.querySelector('.profile-name')?.textContent,
title: document.querySelector('.profile-title')?.textContent,
company: document.querySelector('.company')?.textContent,
}
chrome.runtime.sendMessage({ type: 'find_email', profile }, (response) => {
if (response.email) injectEmailUI(response.email)
})
}
}
function injectEmailUI(email) {
const button = document.createElement('div')
button.className = 'yourapp-extension-email'
button.textContent = email
document.querySelector('.profile-section').appendChild(button)
}
// Run on DOM ready + on URL change (SPA navigation)
detectProfile()
const observer = new MutationObserver(() => detectProfile())
observer.observe(document.body, { childList: true, subtree: true })
CSS isolation:
- Use Shadow DOM
- Or namespace your classes (.yourapp-extension-*)
- Avoid !important hacks
Performance:
- Don't observe large subtrees aggressively
- Debounce mutations
- Defer non-critical work to idle
Implement:
1. Content script for target sites
2. UI injection patterns
3. Style isolation
4. Communication with SW
5. Performance + cleanup
Output: content scripts that don't break host sites.
7. Update + distribution
Browser extensions update through stores.
Distribution channels:
CHROME WEB STORE
- Submission via Developer Dashboard
- Review takes 1-7 days typically
- $5 one-time developer fee
- Single ZIP upload
- Update reviews can take longer
EDGE ADD-ONS
- Microsoft Edge Add-ons store
- Free
- Faster review (often 1-2 days)
- Same package as Chrome (mostly)
FIREFOX ADD-ONS (AMO)
- Mozilla AMO
- Free
- Review can vary (1-7 days; auto-publish for trusted devs)
- Different manifest tweaks
SAFARI APP STORE
- Distribute via Mac App Store
- $99/yr Apple Developer
- More rigorous review
- Xcode wrapper required
Update flow:
- New version bumped in manifest.json
- Submit to each store
- Auto-update for users (within 24-48h typically)
- Force-update mechanism if critical (via app-side check)
Versioning:
- Semver: major.minor.patch
- Track in CRM what version users have (telemetry)
- Sunset old versions if breaking changes
Self-hosted (enterprise):
- Some enterprise customers want self-hosted extension
- Distributed via group policy (Chrome Enterprise)
- Update via private CRX hosting
Implement:
1. CI/CD pipeline for builds per browser
2. Submission scripts per store
3. Telemetry for version distribution
4. Force-update mechanism (rarely; for critical security)
Output: distribution + update mechanics.
8. Operational realities + edge cases
Walk me through:
1. Service worker terminated mid-operation
- MV3 SW can be killed; restart on next message
- Persist state in chrome.storage
- Don't rely on in-memory state
2. User on flaky network
- API calls fail; retry with backoff
- Cache last-known state
- Show "offline" UI
3. User logged out of main app
- Detect 401 from API
- Prompt re-login (open new tab to login page)
- Don't break passively
4. Page changes mid-action (SPA navigation)
- MutationObserver to detect URL changes
- Cleanup + re-detect on URL change
- Cancel in-flight requests if no longer relevant
5. User has multiple devices / browsers
- chrome.storage.sync for cross-device sync (4KB limit; small data)
- chrome.storage.local for local-only (10MB)
- Cloud-side state for large data
6. Popup closes mid-action
- Popup ephemeral; closes on click-away
- Long operations should run in SW
- Use chrome.action.setBadgeText for status
7. Browser updates break manifest
- MV2 → MV3 was the big one (2024 deadline)
- Future changes possible; monitor browser release notes
- CI tests against beta browser releases
8. Privacy / legal
- GDPR / CCPA compliance for any data collection
- Privacy policy required by all stores
- Don't collect data you don't need
9. Extension blocks (corporate firewall)
- Some enterprises block all extensions
- Some allowlist specific extensions
- Document for IT teams
10. Performance impact
- Slow extension = slow browser; users uninstall
- Profile in DevTools
- Lazy-load; don't run on every page if narrow scope
11. Extension compatibility (with other extensions)
- Conflicts possible (CSS clobbering; event handler races)
- Test with popular extensions installed
- Use Shadow DOM aggressively
12. Bug reports + telemetry
- Sentry / similar for crash reporting
- Usage analytics (opt-in; transparent)
- Version tracking for support
13. Store rejection
- Common reasons: excessive permissions, unclear privacy, non-functional
- Address feedback; resubmit
- Appeal process available
14. Managed enterprise deployment
- Chrome Enterprise: Group Policy auto-install
- Documentation for IT admins
- Settings via policy
For each: code change + UX impact + ops consideration.
Output: ops handling.
9. Recap
What you've built:
- Manifest v3 extension scaffold
- Service worker + content script + popup architecture
- Cross-browser support (Chrome / Edge primary; Firefox / Safari secondary)
- Auth via shared cookie / OAuth fallback
- Minimal-permissions design
- Content script with UI injection patterns
- State persistence via chrome.storage
- Submission to Chrome Web Store + Edge Add-ons
- Telemetry + crash reporting
- Operational handling for SW termination, network issues, updates
What you're explicitly NOT shipping in v1:
- Safari (defer; Xcode-wrapped App Store path)
- Firefox (defer if Chrome+Edge gets you 90%)
- Mobile browsers (limited extension support)
- Browser-specific tricks (defer; focus cross-browser)
- AI features inside extension (defer; specialty work)
Ship Level 1 in 2-4 weeks. Level 2 in 8-16 weeks. Level 3 in 16-32 weeks if extension is competitive moat.
The biggest mistake teams make: building extensions speculatively. Without customer pull, extensions are unused — and they're significant maintenance burden.
The second mistake: too many permissions. Users see "this extension can read all your data" and uninstall. Be ruthlessly minimal.
The third mistake: skipping cross-browser early. Code locked to Chrome-specific APIs is hard to retrofit. Use polyfill from day 1.
See Also
- Mobile Push Notifications — sister category (different platform)
- Mobile Deep Linking & Universal Links — sister category
- Public API — depended-upon (extension calls API)
- OAuth Provider Implementation — depended-upon (extension auth)
- Public Share Links & Permissioned Sharing — pairs (extension can share)
- In-Product AI Search & Q&A — pairs (AI inside extension surfaces)
- In-Product Help Center & Knowledge Base — pairs (help inside extension)
- SSO / Enterprise Auth — adjacent (enterprise auth)
- Roles & Permissions — depended-upon
- Multi-Tenancy — depended-upon
- Audit Logs — pairs for extension activity logging
- Performance Optimization — adjacent (extension perf)
- Error Handling & Error Pages — adjacent
- Customer Reports & Scheduled Exports — adjacent (extension can export)
- Custom Fields & Schema Extensibility — pairs