Browser Extension Manager (BXM) is a comprehensive framework for building modern browser extensions. It provides a template-based development system with built-in build tools, component architecture, theming support, and best practices for creating cross-browser extensions.
- Run
npx bxm setupto initialize the project - Run
npm startto start development - Load
packaged/raw/directory in browser as unpacked extension - Edit files in
src/- changes auto-reload via WebSocket
- Run
npm startto watch and compile framework changes - Test changes in a consuming project by linking:
npm link(in BXM) thennpm link browser-extension-manager(in consuming project) - Changes in
src/compile todist/automatically
Extensions are organized around components, each representing a distinct part:
Core Components:
background- Service worker (background script)popup- Browser action popupoptions- Options/settings pagesidepanel- Chrome side panelcontent- Content scripts injected into web pagespages- Custom extension pages (e.g., dashboard, welcome)offscreen- Persistent offscreen document (WebSocket, long-running tasks)
Each component has three parts:
- View - HTML in
src/views/[component]/index.html - Styles - SCSS in
src/assets/css/components/[component]/index.scss - Script - JS in
src/assets/js/components/[component]/index.js
Compilation output:
dist/views/[component]/index.htmldist/assets/css/components/[component].bundle.cssdist/assets/js/components/[component].bundle.js
Framework Layer (src/ in this repository):
- Core framework code and components
- Default templates and configurations
- Build system and tooling
- Published to npm as
browser-extension-manager
Project Layer (consuming extension projects):
- User's extension-specific code
- Overrides and customizations
- Receives defaults from
src/defaults/via thedefaultsgulp task
browser-extension-manager/
├── src/ # Framework source code
│ ├── assets/ # Framework assets
│ │ ├── css/ # SCSS framework
│ │ │ ├── browser-extension-manager.scss # Main entry point
│ │ │ ├── core/ # Core styles (utilities, animations, initialize)
│ │ │ ├── components/ # Component-specific styles (popup, options, content, pages)
│ │ │ └── bundles/ # Custom bundle SCSS files
│ │ ├── js/ # JavaScript framework
│ │ │ └── main.js
│ │ ├── themes/ # Theme system
│ │ │ ├── _template/ # Template for creating new themes
│ │ │ ├── bootstrap/ # Bootstrap theme (full Bootstrap 5 source)
│ │ │ └── classy/ # Classy theme (Bootstrap + custom design system)
│ │ └── vendor/ # Vendor assets
│ ├── defaults/ # Default project structure (copied to consuming projects)
│ │ ├── src/
│ │ │ ├── assets/
│ │ │ │ ├── css/
│ │ │ │ │ ├── main.scss # Global styles entry point
│ │ │ │ │ └── components/ # Component-specific overrides
│ │ │ │ ├── js/
│ │ │ │ │ └── components/ # Component JavaScript
│ │ │ │ ├── images/
│ │ │ │ └── vendor/
│ │ │ ├── views/ # HTML views (templates)
│ │ │ ├── manifest.json # Extension manifest (user-editable)
│ │ │ └── _locales/ # Internationalization
│ │ ├── hooks/ # Build hooks
│ │ ├── config/ # Configuration files
│ │ └── CLAUDE.md # Project documentation template
│ ├── config/ # Framework configuration
│ │ ├── manifest.json # Default manifest configuration
│ │ └── page-template.html # HTML template for views
│ ├── gulp/ # Build system
│ │ ├── main.js # Gulp entry point
│ │ ├── tasks/ # Gulp tasks
│ │ │ ├── defaults.js # Copy default files to project
│ │ │ ├── distribute.js # Distribute project files to dist/
│ │ │ ├── sass.js # Compile SCSS
│ │ │ ├── webpack.js # Bundle JavaScript
│ │ │ ├── html.js # Process HTML views
│ │ │ ├── icons.js # Generate icon sizes
│ │ │ ├── package.js # Package extension for distribution
│ │ │ ├── audit.js # Run audits
│ │ │ └── serve.js # Development server
│ │ └── plugins/ # Custom plugins
│ │ └── webpack/
│ │ └── strip-dev-blocks.js # Strip dev-only code
│ ├── lib/ # Utility libraries
│ │ ├── extension.js # Cross-browser extension API wrapper
│ │ ├── logger.js # Logging utility
│ │ └── logger-lite.js # Lightweight logger
│ ├── commands/ # CLI commands
│ │ ├── setup.js # Setup project
│ │ ├── clean.js # Clean build artifacts
│ │ └── version.js # Version management
│ ├── build.js # Build manager class
│ ├── background.js # Background script manager
│ ├── popup.js # Popup manager
│ ├── options.js # Options manager
│ ├── sidepanel.js # Sidepanel manager
│ ├── page.js # Page manager
│ ├── content.js # Content script manager
│ └── cli.js # CLI entry point
├── dist/ # Compiled framework (published to npm)
├── bin/ # CLI binaries
│ └── browser-extension-manager
├── package.json
└── CLAUDE.md # This file
When users create a project using BXM, they get this structure:
my-extension/
├── src/
│ ├── assets/
│ │ ├── css/
│ │ │ ├── main.scss # Global styles
│ │ │ └── components/ # Component-specific styles
│ │ ├── js/
│ │ │ └── components/ # Component JavaScript
│ │ ├── images/
│ │ │ └── icon.png # Source icon (1024x1024)
│ │ └── vendor/
│ ├── views/ # HTML templates
│ ├── _locales/ # i18n translations
│ └── manifest.json # Extension manifest
├── config/
│ └── config.json # BXM configuration
├── hooks/
│ ├── build:pre.js # Pre-build hook
│ └── build:post.js # Post-build hook
├── dist/ # Compiled files (gitignored)
├── packaged/ # Packaged extension (gitignored)
│ ├── raw/ # Load this in browser
│ └── extension.zip # Production build
└── package.json
When adding a new component type to the framework:
-
Create framework component styles:
src/assets/css/components/[component]/index.scss -
Create default template files:
src/defaults/src/assets/css/components/[component]/index.scss src/defaults/src/assets/js/components/[component]/index.js src/defaults/src/views/[component]/index.html -
Create manager class (if needed):
// src/[component].js const Manager = require('./build.js'); class ComponentManager extends Manager { constructor(options) { super(options); } } module.exports = ComponentManager;
-
Export in package.json:
{ "exports": { "./component": "./dist/component.js" } }
Key gulp tasks:
- defaults (gulp/tasks/defaults.js) - Copies template files from
dist/defaults/to consuming projects - distribute (gulp/tasks/distribute.js) - Copies project files to
dist/ - sass (gulp/tasks/sass.js) - Compiles SCSS with sophisticated load path system
- webpack (gulp/tasks/webpack.js) - Bundles JavaScript with Babel
- html (gulp/tasks/html.js) - Processes HTML views into templates (see HTML Templating below)
- package (gulp/tasks/package.js) - Creates packaged extension
HTML views in src/views/ are processed through a two-step templating system using {{ }} brackets.
Available variables:
{{ brand.name }}- Brand name from config{{ brand.url }}- Brand URL from config{{ page.name }}- Component name (e.g.,popup,pages/index){{ page.path }}- Full view path{{ page.title }}- Page title (defaults to brand name){{ theme.appearance }}- Theme appearance (darkorlight){{ cacheBust }}- Cache-busting timestamp
Example usage in views:
<a href="{{ brand.url }}/pricing">Upgrade to Premium</a>
<p>Welcome to {{ brand.name }}</p>How it works:
- Your view file (
src/views/[component]/index.html) is templated first - The result is injected into page-template.html
- The outer template is processed with the same variables
- serve (gulp/tasks/serve.js) - WebSocket server for live reload
Theme location: src/assets/themes/
Available themes:
bootstrap- Pure Bootstrap 5.3+classy- Bootstrap + custom design system_template- Template for new themes
Theme structure:
src/assets/themes/[theme-id]/
├── _config.scss # Theme variables (with !default)
├── _theme.scss # Theme entry point
├── scss/ # Theme-specific SCSS
└── js/ # Theme-specific JS
To create a new theme:
- Copy
_template/to new directory - Customize
_config.scssvariables - Add theme-specific styles in
scss/ - Users activate via
config/config.json
Location: src/defaults/
How it works:
- Files in
src/defaults/are the starter template - During build, they're copied to
dist/defaults/ - When users run
npx bxm setup, files copy fromdist/defaults/to their project - File behavior controlled by
FILE_MAPin gulp/tasks/defaults.js
File map rules:
const FILE_MAP = {
'src/**/*': { overwrite: false }, // Never overwrite user code
'hooks/**/*': { overwrite: false }, // Never overwrite hooks
'.nvmrc': { template: { node: '22' } }, // Template with data
// ...
};Rule types:
overwrite: false- Never replace if existsoverwrite: true- Always updateskip: function- Dynamic skip logictemplate: data- Run templatingname: function- Rename file
Main entry: src/assets/css/browser-extension-manager.scss
Core modules:
- core/_initialize.scss - Base resets
- core/_utilities.scss - Utility classes
- core/_animations.scss - Animations
Component system:
Each component can have framework defaults in src/assets/css/components/[name]/index.scss
Load path resolution:
- Framework CSS (
node_modules/browser-extension-manager/dist/assets/css) - Active theme (
node_modules/browser-extension-manager/dist/assets/themes/[theme-id]) - Project dist (
dist/assets/css) - node_modules
This enables:
@use 'browser-extension-manager' as * with ($primary: #5B47FB);
@use 'theme' as *; // Resolves to active theme
@use 'components/popup' as *; // Import default component stylesManager classes: src/background.js, src/popup.js, src/options.js, src/sidepanel.js, src/page.js, src/content.js
Extension API wrapper: src/lib/extension.js
A universal/agnostic API wrapper that enables cross-browser extension development. Write your extension once and it works on Chrome, Firefox, Edge, and other browsers.
How it works:
- Detects and normalizes APIs from
chrome.*,browser.*, andwindow.*namespaces - Automatically selects the correct API based on what's available in the current browser
- Exports a singleton with unified access to all extension APIs
Supported APIs:
action, alarms, bookmarks, browsingData, browserAction, certificateProvider, commands, contentSettings, contextMenus, cookies, debugger, declarativeContent, declarativeNetRequest, devtools, dns, documentScan, downloads, enterprise, events, extension, extensionTypes, fileBrowserHandler, fileSystemProvider, fontSettings, gcm, history, i18n, identity, idle, input, instanceID, management, notifications, offscreen, omnibox, pageAction, permissions, platformKeys, power, printerProvider, privacy, proxy, runtime, scripting, search, sessions, sidePanel, storage, tabGroups, tabs, topSites, tts, ttsEngine, userScripts, vpnProvider, wallpaper, webNavigation, webRequest, windows
Usage:
// Exposed via manager - no separate import needed
const Manager = new (require('browser-extension-manager/popup'));
Manager.initialize().then(() => {
const { extension } = Manager;
// Works on Chrome, Firefox, Edge, etc.
extension.tabs.query({ active: true }, (tabs) => { ... });
extension.storage.get('key', (result) => { ... });
extension.runtime.sendMessage({ type: 'hello' });
});Storage normalization:
The wrapper automatically resolves storage to storage.sync if available, falling back to storage.local.
Auth Helpers: src/lib/auth-helpers.js
Provides cross-context auth synchronization and reusable auth UI event handlers.
Background.js is the source of truth for authentication. Browser extensions have multiple isolated JavaScript contexts (background, popup, options, pages, sidepanel) - each runs its own Firebase instance. BXM syncs them via messaging (no storage).
Sign-in Flow:
User clicks .auth-signin-btn
→ openAuthPage() opens https://{authDomain}/token?authSourceTabId=123
→ Website authenticates, redirects to /token?authToken=xxx
→ background.js tabs.onUpdated detects authDomain URL with authToken param
→ background.js signs in with signInWithCustomToken()
→ background.js broadcasts token to all open contexts via postMessage()
→ Closes the /token tab, reactivates original tab
→ Open contexts receive broadcast and sign in with the token
Context Load Flow:
Context loads (popup, page, options, sidepanel)
→ Web Manager initializes, waits for auth to settle via auth.listen({once: true})
→ Sends bxm:syncAuth message to background with local UID
→ Background compares UIDs:
→ Same UID (including both null) → already in sync, no action
→ Different UID → background fetches fresh custom token from server, sends to context
→ Background signed out, context signed in → tells context to sign out
Sign-out Flow:
User clicks .auth-signout-btn
→ Web Manager signs out that context's Firebase
→ setupSignOutListener() detects sign-out, sends bxm:signOut to background
→ background.js signs out its Firebase
→ background.js broadcasts bxm:signOut to all other contexts
→ All contexts sign out
Key Implementation Details:
-
No storage: Auth state is NOT stored in
chrome.storage. Firebase persists sessions in IndexedDB per context. Web Manager handles UI bindings. -
Firebase in service workers: Static ES6 imports are required. Dynamic
import()fails with webpack chunking in service workers. -
Config path:
authDomainis atconfig.firebase.app.config.authDomain(loaded via BXM_BUILD_JSON). -
Required permission:
tabspermission needed fortabs.onUpdatedlistener.
Functions:
syncWithBackground(context)- Compares context's UID with background's UID on load, syncs if differentsetupAuthBroadcastListener(context)- Listens for sign-in/sign-out broadcasts from backgroundsetupSignOutListener(context)- Notifies background when context signs outsetupAuthEventListeners(context)- Sets up delegated click handlers for auth buttonsopenAuthPage(context, options)- Opens auth page on the website with authSourceTabId for tab restoration
Auth Button CSS Classes:
Add these classes to your HTML elements to enable automatic auth handling:
| Class | Description | Action |
|---|---|---|
.auth-signin-btn |
Sign in button | Opens /token page on website (authDomain) |
.auth-signout-btn |
Sign out button | Handled by Web Manager (triggers storage sync via auth listener) |
.auth-account-btn |
Account button | Opens /account page on website |
Example usage:
<!-- Sign In Button (shown when logged out) -->
<button class="btn auth-signin-btn" data-wm-bind="@show !auth.user">
Sign In
</button>
<!-- Account Dropdown (shown when logged in) -->
<div data-wm-bind="@show auth.user" hidden>
<a class="auth-account-btn" href="#">Account</a>
<button class="auth-signout-btn">Sign Out</button>
</div>Reactive bindings:
data-wm-bind="@show auth.user"- Show when logged indata-wm-bind="@show !auth.user"- Show when logged outdata-wm-bind="@text auth.user.displayName"- Display user's namedata-wm-bind="@text auth.user.email"- Display user's emaildata-wm-bind="@attr src auth.user.photoURL"- Set avatar image src
Logger: src/lib/logger.js
- Full logging utility
- src/lib/logger-lite.js for lightweight contexts
Template replacement: Webpack plugin replaces markers during build:
%%% version %%%→ package version%%% brand.name %%%→ brand name%%% environment %%%→ 'production' or 'development'%%% liveReloadPort %%%→ WebSocket port%%% webManagerConfiguration %%%→ JSON config
Hook locations:
hooks/build:pre.js- Before packaginghooks/build:post.js- After packaging
Hook structure:
module.exports = async function (index) {
// index contains build information
console.log('Hook running...');
};Implementation: src/gulp/tasks/package.js loads and executes hooks
Entry point: bin/browser-extension-manager
CLI implementation: src/cli.js
Commands: src/commands/
- setup.js - Initialize project
- clean.js - Clean build artifacts
- version.js - Show version
Aliases in package.json:
{
"bin": {
"ext": "bin/browser-extension-manager",
"xm": "bin/browser-extension-manager",
"bxm": "bin/browser-extension-manager",
"browser-extension-manager": "bin/browser-extension-manager"
}
}- Keep framework code in src/ - Never edit
dist/directly - Use modular design - Per Ian's standards, build modular code, not one giant file
- Maintain defaults/ - Keep template files up-to-date
- Document changes - Update CLAUDE.md
Short-circuit pattern:
// Good
const $el = document.querySelector('...');
if (!$el) {
return;
}
// Long code block
// Bad
if ($el) {
// Long code block
}Logical operators:
// Good
const a = b
|| c
|| d;
// Bad
const a = b ||
c ||
d;DOM element naming:
const $submitBtn = document.querySelector('#submit');
const $emailInput = document.querySelector('#email');File operations:
// Prefer fs-jetpack
const jetpack = require('fs-jetpack');
jetpack.write('file.txt', 'content');Per Ian's instructions:
- DO NOT make changes backwards compatible unless explicitly requested
- Most changes are for unreleased code or local development
- If we develop something and change it later, just change it - don't maintain old way
- Only add backwards compatibility when specifically asked
Commit:
src/- All framework source codepackage.json- Package configuration- Documentation (CLAUDE.md)
Ignore:
dist/- Compiled framework (generated by prepare-package)node_modules/
- Make changes in src/
- Compile:
npm start(watches and compiles to dist/) - Link locally:
npm link
- In consuming project:
npm link browser-extension-manager npm start
- Test in browser:
Load
packaged/raw/directory
Test individual gulp tasks:
cd consuming-project/
npm run gulp -- [task-name]Available tasks:
defaults- Test template copyingsass- Test SCSS compilationwebpack- Test JS bundlinghtml- Test HTML processingpackage- Test extension packaging
In this repository:
./bin/browser-extension-manager setup
./bin/browser-extension-manager clean
./bin/browser-extension-manager versionOr via npm:
npx bxm setup
npx bxm clean
npx bxm version- Update version in package.json
- Compile framework:
npm run prepare
- Test in consuming project
- Update documentation if needed
npm publishPublished package includes:
dist/- Compiled frameworkbin/- CLI binariespackage.jsonREADME.md
Excluded via .npmignore:
src/- Source code (users don't need this)- Development files
Location: src/assets/css/core/_utilities.scss
// Add new utility
.shadow-lg {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}Location: src/assets/themes/classy/_config.scss (or relevant theme)
$new-color: #FF6B6B !default;
// Make available to consumers
@forward '../bootstrap/scss/bootstrap.scss' with (
$new-color: $new-color
);Location: src/gulp/tasks/webpack.js
resolve: {
alias: {
'__new_alias__': path.resolve(paths.root, 'path/to/directory'),
}
}Location: src/gulp/tasks/webpack.js
new ReplacePlugin({
patterns: [
{
find: /%%% newVariable %%%/g,
replacement: 'value'
}
]
})Location: src/config/manifest.json
{
// Add new default permission
permissions: [
'storage',
'newPermission'
]
}Compilation: src/gulp/tasks/package.js merges user manifest with defaults
- Rebuild framework:
npm run prepare - Reinstall in consuming project:
npm install browser-extension-manager@latest - Or use local link:
npm link(in BXM) thennpm link browser-extension-manager(in project)
- Check task file: src/gulp/tasks/
- Verify paths are correct
- Check for syntax errors
- Test task individually:
npm run gulp -- task-name
- Check load paths in src/gulp/tasks/sass.js
- Verify theme structure
- Check for circular imports
- Test with minimal SCSS to isolate issue
- Check pattern in src/gulp/tasks/webpack.js
- Verify replacement value is correct
- Check if file is processed by webpack
BXM integrates Web Manager for Firebase, analytics, and web services functionality. Each component manager initializes Web Manager automatically with configuration from config/config.json.
Web Manager API:
- Study the Web Manager API and documentation in the sibling repository
- Location:
../web-manager/(relative to this project) - GitHub: https://github.com/itw-creative-works/web-manager
- npm: https://www.npmjs.com/package/web-manager
Usage in BXM:
const Manager = new (require('browser-extension-manager/popup'));
Manager.initialize().then(() => {
const { webManager } = Manager;
// Web Manager provides:
// - Firebase (Firestore, Auth, Storage, etc.)
// - Analytics
// - User management
// - Utilities
const db = webManager.libraries.firebase.firestore();
});- Gulp - Build system and task automation
- Webpack - JavaScript bundling with Babel transpilation
- Sass - CSS preprocessing with advanced features
- fs-jetpack - File operations (per Ian's preference)
- wonderful-fetch - HTTP requests
- wonderful-version - Version management
- ws - WebSocket server for live reload
- GitHub: https://github.com/itw-creative-works/browser-extension-manager
- npm: https://www.npmjs.com/package/browser-extension-manager
- Chrome Extensions: https://developer.chrome.com/docs/extensions/
- Firefox Add-ons: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions
Build System:
- src/gulp/main.js - Gulp entry point
- src/gulp/tasks/ - All build tasks
- src/build.js - Build manager base class
Manager Classes:
- src/background.js - Background script manager
- src/popup.js - Popup manager
- src/options.js - Options manager
- src/sidepanel.js - Sidepanel manager
- src/page.js - Custom page manager
- src/content.js - Content script manager
Utilities:
- src/lib/extension.js - Cross-browser API wrapper
- src/lib/auth-helpers.js - Cross-context auth sync (see Auth Architecture section above)
- src/lib/logger.js - Logging utility
- src/cli.js - CLI implementation
Auth System (Cross-Context):
- src/background.js - Source of truth;
handleSyncAuth(),handleSignOut(),broadcastAuthToken() - src/lib/auth-helpers.js -
syncWithBackground(),setupAuthBroadcastListener(),setupSignOutListener()
CSS Framework:
- src/assets/css/browser-extension-manager.scss - Main entry
- src/assets/css/core/ - Core styles
- src/assets/css/components/ - Component styles
- src/assets/themes/ - Theme system
Templates:
- src/defaults/ - Project starter template
- src/config/manifest.json - Default manifest
- src/config/page-template.html - HTML template
CLI:
- bin/browser-extension-manager - CLI entry
- src/commands/ - CLI commands