Overview
Chrome Extensions use service workers to listen for Chrome API events in the background. CRXJS provides HMR support and automatic configuration for background scripts.
Service Worker Configuration
Add a service worker to your extension in the manifest under background.service_worker. CRXJS uses module-type service workers because Vite uses the ES module format.
{
"background" : {
"service_worker" : "src/background.ts" ,
"type" : "module"
}
}
The type: "module" field is required for ES module service workers.
How CRXJS Handles Background Scripts
CRXJS creates a loader file at the root of your extension to ensure the service worker behaves consistently in development and production.
Development Mode
During development, CRXJS:
Creates a loader file that imports from the Vite dev server
Includes the HMR client for hot updates
Loads your service worker code from localhost
From the source code (plugin-background.ts:82):
// Development loader
loader = `import ' ${ proto } ://localhost: ${ port } /@vite/env'; \n `
loader += `import ' ${ proto } ://localhost: ${ port }${ workerClientId } '; \n `
if ( worker )
loader += `import ' ${ proto } ://localhost: ${ port } / ${ worker } '; \n `
Production Mode
In production, the loader simply imports your bundled service worker:
import './src/background.js' ;
Hot Module Replacement
Changes to background scripts trigger a full extension reload because service workers control the entire extension lifecycle.
From the source code (plugin-hmr.ts:122):
// Check if changed file is a background dependency
if ( inputManifestFiles . background . length ) {
const background = prefix ( '/' , inputManifestFiles . background [ 0 ])
if (
relFiles . has ( background ) ||
modules . some ( isImporter ( join ( server . config . root , background )))
) {
debug ( 'sending runtime reload' )
server . ws . send ( crxRuntimeReload )
}
}
Unlike content scripts and pages, background scripts cannot hot reload without restarting the entire extension.
Service Worker Examples
Basic Service Worker
console . log ( 'Background service worker loaded' )
// Listen for extension installation
chrome . runtime . onInstalled . addListener (() => {
console . log ( 'Extension installed' )
})
// Handle messages from content scripts
chrome . runtime . onMessage . addListener (( message , sender , sendResponse ) => {
console . log ( 'Received message:' , message )
sendResponse ({ status: 'ok' })
})
Using Chrome APIs
// Listen for tab updates
chrome . tabs . onUpdated . addListener (( tabId , changeInfo , tab ) => {
if ( changeInfo . status === 'complete' && tab . url ) {
console . log ( 'Tab loaded:' , tab . url )
}
})
// Handle keyboard commands
chrome . commands . onCommand . addListener (( command ) => {
console . log ( 'Command:' , command )
})
// Manage storage
chrome . storage . local . set ({ key: 'value' })
chrome . storage . local . get ([ 'key' ], ( result ) => {
console . log ( 'Value:' , result . key )
})
Importing Dependencies
// Import npm packages
import { nanoid } from 'nanoid'
// Import utilities
import { logger } from './utils/logger'
// Import types
import type { Message } from './types'
chrome . runtime . onMessage . addListener (( message : Message ) => {
const id = nanoid ()
logger . log ( 'Message received with ID:' , id )
})
Firefox Support
Firefox uses background scripts instead of service workers. CRXJS handles this automatically:
if ( browser === 'firefox' ) {
manifest . background = {
scripts: [ this . getFileName ( refId )],
type: 'module' ,
}
}
Use the same code for both browsers - CRXJS adapts the manifest format.
Service Worker Scope
Service workers can only intercept requests within their scope. CRXJS places the loader at the extension root to ensure full coverage.
TypeScript Support
Use TypeScript with full Chrome API types:
// Install @types/chrome
import type { Tabs } from 'chrome'
chrome . tabs . query ({ active: true , currentWindow: true }, ( tabs : Tabs . Tab []) => {
const currentTab = tabs [ 0 ]
console . log ( 'Current tab:' , currentTab . url )
})
Debugging
During development, inspect your service worker:
Open chrome://extensions
Enable Developer mode
Find your extension and click service worker
Use the DevTools console to debug
Manifest Configure background scripts
HMR Understand extension reloading
Content Scripts Communicate with content scripts
Learn More