Chrome Extensions have special security constraints. Our SDK provides a dedicated transport layer that handles message passing between content scripts, background scripts, and web pages.
Overview
The Composite Analytics SDK includes first-class support for Chrome Extensions with:
Automatic Content Script Transport - Handles message passing between isolated contexts
Manifest V3 Support - Full compatibility with the latest Chrome Extension manifest
Session Recording - Capture user interactions across all tabs
User Identification - Identify users across extension contexts
Optimized Batching - Efficient batching to minimize API calls
Installation
Install the SDK in your Chrome Extension project:
npm install @composite-inc/composite-js
Manifest Configuration
Add the required permissions and host permissions to your manifest.json:
{
"manifest_version" : 3 ,
"name" : "Your Extension" ,
"version" : "1.0.0" ,
"permissions" : [
"storage" ,
"tabs"
],
"host_permissions" : [
"https://prod.alb.us.api.composite.com/*"
],
"background" : {
"service_worker" : "background.js"
},
"content_scripts" : [
{
"matches" : [ "<all_urls>" ],
"js" : [ "content.js" ]
}
]
}
Basic Setup
Background Script
Initialize the SDK in your background script (service worker for Manifest V3):
// background.js
import composite from '@composite-inc/composite-js' ;
// Initialize with Chrome Extension transport
chrome . runtime . onInstalled . addListener ( async () => {
await composite . init ({
apiKey: 'pk_your_api_key' ,
transport: 'chrome-extension' , // Automatically uses ContentScriptTransport
debug: true
});
});
Content Script
Enable session recording from content scripts that run on web pages:
// content.js
import composite from '@composite-inc/composite-js' ;
// Initialize in content script with session recording
( async () => {
await composite . init ({
apiKey: 'pk_your_api_key' ,
transport: 'chrome-extension' ,
sessionRecording: true
});
})();
Initialize the SDK from your extension’s popup:
// popup.js
import composite from '@composite-inc/composite-js' ;
document . addEventListener ( 'DOMContentLoaded' , async () => {
await composite . init ({
apiKey: 'pk_your_api_key' ,
transport: 'chrome-extension'
});
// Identify user when they log in
document . getElementById ( 'login-button' )?. addEventListener ( 'click' , async () => {
const user = await authenticateUser ();
composite . identify ( user . id , {
email: user . email ,
plan: user . plan
});
});
});
Session Recording in Extensions
Enable session recording to capture user interactions across all tabs:
// content.js - Session recording setup
import composite from '@composite-inc/composite-js' ;
await composite . init ({
apiKey: 'pk_your_api_key' ,
transport: 'chrome-extension' ,
sessionRecording: {
maskAllInputs: true , // Mask sensitive input fields
blockSelector: '.sensitive' , // Block specific elements
maskTextSelector: '.private' , // Mask text content
compress: true , // Enable compression
recordCanvas: false , // Disable canvas recording for performance
recordCrossOriginIframes: false
}
});
// Manually control recording
composite . startSessionRecording (); // Start recording
composite . stopSessionRecording (); // Stop recording
Session recording can impact performance. Use selectors to mask sensitive content and consider disabling canvas recording for better performance.
Advanced Configuration
Cross-Context Communication
The SDK handles message passing between different extension contexts automatically:
// Shared configuration for all contexts
const compositeConfig = {
apiKey: 'pk_your_api_key' ,
transport: 'chrome-extension' ,
sessionRecording: true ,
debug: process . env . NODE_ENV === 'development' ,
// Custom transport configuration
transportOptions: {
flushInterval: 2000 , // Batch events every 2 seconds
maxBatchSize: 50 , // Max events per batch
retryLimit: 3 , // Retry failed requests
timeout: 10000 // Request timeout
}
};
// Use same config in all contexts
await composite . init ( compositeConfig );
Storage and Persistence
The SDK automatically uses Chrome’s storage API for persistence:
// User identification persists across sessions
composite . identify ( 'user_123' , {
email: 'user@example.com' ,
subscription: 'premium'
});
// Reset on logout
document . getElementById ( 'logout' )?. addEventListener ( 'click' , () => {
composite . reset (); // Clears user identity
});
User Identification
Identify users across extension contexts:
// Identify user when they authenticate
async function onUserLogin ( user ) {
composite . identify ( user . id , {
email: user . email ,
plan: user . plan ,
created_at: user . createdAt
});
}
// Reset on logout
document . getElementById ( 'logout' )?. addEventListener ( 'click' , () => {
composite . reset (); // Clears user identity
});
Choose your build tool setup:
Vite
Webpack
Plain JavaScript
Chrome extensions require different build configurations for content scripts vs background/popup scripts: // vite.config.js
import { defineConfig } from 'vite' ;
import { resolve } from 'path' ;
// Content script needs IIFE format (no ES modules in content scripts)
const contentConfig = {
build: {
rollupOptions: {
input: {
content: resolve ( __dirname , 'src/content.js' )
},
output: {
format: 'iife' ,
entryFileNames: '[name].js' ,
inlineDynamicImports: true
}
},
outDir: 'dist' ,
emptyOutDir: false
}
};
// Background and popup can use ES modules
const mainConfig = {
build: {
rollupOptions: {
input: {
background: resolve ( __dirname , 'src/background.js' ),
popup: resolve ( __dirname , 'src/popup.js' )
},
output: {
entryFileNames: '[name].js' ,
chunkFileNames: '[name].js' ,
assetFileNames: '[name].[ext]'
}
},
outDir: 'dist' ,
emptyOutDir: false
}
};
// Export based on mode: `vite build` vs `vite build --mode content`
export default defineConfig (({ mode }) => {
if ( mode === 'content' ) {
return contentConfig ;
}
return mainConfig ;
} ) ;
Build both configurations: # Build background and popup scripts
vite build
# Build content script separately
vite build --mode content
Content scripts run in an isolated context and cannot use ES modules. Use separate build configs with format: 'iife' and inlineDynamicImports: true for content scripts.
Dynamic Loading
Load the SDK only when needed:
// Lazy load for popup
document . addEventListener ( 'DOMContentLoaded' , async () => {
if ( userHasOptedIn ()) {
const { default : composite } = await import ( '@composite-inc/composite-js' );
await composite . init ({
apiKey: 'pk_your_api_key' ,
transport: 'chrome-extension'
});
}
});
Debugging
Enable debug mode to see detailed logs:
await composite . init ({
apiKey: 'pk_your_api_key' ,
transport: 'chrome-extension' ,
debug: true // Enables console logging
});
// Check transport status
console . log ( 'Transport ready:' , composite . isInitialized ());
// Manually flush events
await composite . flush ();
Open Chrome DevTools for your extension
Check the Network tab for requests to prod.alb.us.api.composite.com
Use the Console to see debug logs
Check Application > Storage for persisted data
Common Issues
Content Security Policy (CSP) Errors
If you encounter CSP errors, ensure your manifest includes the correct host permissions: "host_permissions" : [
"https://prod.alb.us.api.composite.com/*"
]
For inline scripts, you may need to adjust your CSP: "content_security_policy" : {
"extension_pages" : "script-src 'self'; object-src 'self'"
}
The SDK handles message passing automatically, but ensure:
Background script is loaded before content scripts
Same API key is used across all contexts
Transport is set to 'chrome-extension' in all contexts
Session Recording Not Working
Session recording requires:
Content script injected with <all_urls> or specific matches
sessionRecording: true in configuration
Sufficient permissions in manifest
Test with a simple page first, then expand to more complex sites.
Session Recordings Not Appearing in Dashboard
Check:
API key is correct
Network requests are successful (check DevTools)
Debug mode is enabled to see logs
Session recording is enabled in config
Best Practices
Minimize Permissions Only request the permissions your extension actually needs
Mask Sensitive Data Use selectors to mask passwords, credit cards, and PII in recordings
Handle Errors Implement error boundaries and fallbacks for SDK failures
Identify Users Always identify logged-in users to connect their sessions
Example: Complete Extension
Here’s a complete example of a Chrome Extension with Composite Analytics:
background.js
content.js
popup.js
manifest.json
import composite from '@composite-inc/composite-js' ;
// Initialize on install
chrome . runtime . onInstalled . addListener ( async ( details ) => {
await composite . init ({
apiKey: 'pk_your_api_key' ,
transport: 'chrome-extension'
});
});
What’s Next?