Debugging
Learn effective debugging techniques for Chrome extensions across different contexts.
Overview
Chrome extensions run in multiple isolated contexts, each requiring different debugging approaches:
- Background Service Worker — Background tasks and lifecycle
- Content Scripts — Injected into web pages
- Popup/Options Pages — Extension UI pages
Debugging Background Service Worker
Access DevTools
- Navigate to
chrome://extensions/ - Ensure Developer mode is enabled
- Find your extension
- Click Inspect views: background page (or service worker)
Console Logging
import { logger } from '@/shared/lib/logger';
chrome.runtime.onInstalled.addListener((details) => {
logger.info('Extension installed', { reason: details.reason, version: details.previousVersion });
});
chrome.runtime.onStartup.addListener(() => {
logger.info('Extension started');
});
Breakpoints
- Open background service worker DevTools
- Go to Sources tab
- Find your script (
static/js/background.js) - Click line numbers to set breakpoints
- Reload extension to trigger breakpoints
Network Requests
Monitor API calls in the Network tab:
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
logger.info('Data fetched', data);
};
Network tab shows:
- Request/response headers
- Payload data
- Timing information
- Status codes
Storage Inspection
View Chrome Storage in Application tab:
- Open background DevTools
- Navigate to Application → Storage
- Expand Chrome Storage → Local/Sync
- View and edit stored data
Debugging Content Scripts
Access DevTools
Content scripts run in the context of web pages:
- Open the webpage where content script is injected
- Right-click → Inspect
- Content script logs appear in Console
- Filter by extension name:
[Chrome-Extension-Starter]
Console Logging
import { logger } from '@/shared/lib/logger';
logger.info('Content script loaded');
bus.on(MSG.CHANGE_BG, (payload) => {
logger.debug('Received message', payload);
document.body.style.backgroundColor = payload.color;
return { ok: true };
});
Breakpoints
- Open DevTools on the webpage
- Go to Sources tab
- Find content script under Content scripts →
chrome-extension://[id] - Set breakpoints in
content.js
DOM Inspection
Inspect injected elements:
const overlay = mount('ces-overlay');
logger.info('Overlay mounted', overlay);
Elements tab shows:
- Injected DOM structure
- Applied styles
- Event listeners
Message Flow Debugging
Debug message passing:
bus.on(MSG.CHANGE_BG, (payload, sender) => {
logger.debug('Message received from', sender.tab?.id, 'with payload', payload);
try {
document.body.style.backgroundColor = payload.color;
logger.info('Background changed successfully');
return { ok: true };
} catch (err) {
logger.error('Failed to change background', err);
return { ok: false };
}
});
Debugging Popup/Options Pages
Access DevTools
Popup:
- Right-click extension icon in toolbar
- Click Inspect popup
Options:
- Right-click extension icon → Options
- Right-click options page → Inspect
Console Logging
import { logger } from '@/shared/lib/logger';
const Popup = () => {
const handleClick = async () => {
logger.debug('Button clicked');
const result = await bus.sendToActive(MSG.CHANGE_BG, { color: '#0ea5e9' });
logger.info('Message sent, result:', result);
};
return <button onClick={handleClick}>Change Background</button>;
};
Component Inspection
Use React DevTools:
- Install React Developer Tools
- Open popup DevTools
- Access Components tab
- Inspect component state and props
Network Requests
Monitor API calls in popup:
const fetchSettings = async () => {
logger.debug('Fetching settings from API');
const response = await fetch('/api/settings');
const data = await response.json();
logger.info('Settings fetched', data);
return data;
};
Common Debugging Patterns
Log Message Flow
Track messages across contexts:
logger.info('[POPUP] Sending CHANGE_BG message');
await bus.sendToActive(MSG.CHANGE_BG, { color: 'red' });
bus.on(MSG.CHANGE_BG, (payload) => {
logger.info('[CONTENT] Received CHANGE_BG message', payload);
return { ok: true };
});
Conditional Breakpoints
Set breakpoints that only trigger when conditions are met:
- Right-click on breakpoint
- Select Edit breakpoint
- Add condition:
payload.color === 'red'
Performance Profiling
Measure execution time:
const measurePerformance = async (fn: () => Promise<any>, label: string) => {
const start = performance.now();
logger.debug(`[PERF] Starting: ${label}`);
try {
const result = await fn();
const duration = performance.now() - start;
logger.info(`[PERF] Completed: ${label} in ${duration.toFixed(2)}ms`);
return result;
} catch (err) {
const duration = performance.now() - start;
logger.error(`[PERF] Failed: ${label} after ${duration.toFixed(2)}ms`, err);
throw err;
}
};
// Usage
await measurePerformance(
() => bus.sendToActive(MSG.CHANGE_BG, { color: 'blue' }),
'Send CHANGE_BG message'
);
Error Boundary Logging
Catch and log component errors:
import { Component } from 'preact';
import { logger } from '@/shared/lib/logger';
class ErrorBoundary extends Component {
componentDidCatch(error: Error, errorInfo: any) {
logger.error('Component error caught', { error, errorInfo });
}
render() {
return this.props.children;
}
}
// Usage
<ErrorBoundary>
<App />
</ErrorBoundary>
Chrome DevTools Tips
Console Filtering
Filter console logs:
- Click Default levels dropdown
- Select desired levels (Info, Warnings, Errors)
- Use filter box:
[Chrome-Extension-Starter]
Preserve Logs
Keep logs when popup closes:
- Open popup DevTools
- Check Preserve log in Console tab
- Logs persist after popup closes
Source Maps
Enable source maps for debugging TypeScript:
export default defineConfig({
output: {
sourceMap: {
js: 'source-map' // Enable for development
}
}
});
Benefits:
- Debug original TypeScript code
- Set breakpoints in
.tsfiles - Better error stack traces
Debugging Tools
Logger Module
Use the built-in logger for structured logging:
import { logger } from '@/shared/lib/logger';
// Debug level (detailed)
logger.debug('Processing request', { userId: 123, action: 'update' });
// Info level (important events)
logger.info('User logged in', { userId: 123 });
// Warning level (potential issues)
logger.warn('API rate limit approaching', { remaining: 10 });
// Error level (failures)
logger.error('Failed to save settings', error);
Chrome Storage Inspector
View storage in real-time:
// Add to background script
chrome.storage.onChanged.addListener((changes, areaName) => {
logger.info('Storage changed', { area: areaName, changes });
});
Message Debugger
Log all messages:
// In content script
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
logger.debug('Message received', { type: msg.type, payload: msg.payload, sender });
return false; // Allow other listeners to handle
});
Remote Debugging
Chrome Remote Debugging
Debug extensions on remote devices:
- Enable USB debugging on Android device
- Connect via USB
- Open
chrome://inspect/#devices - Click Inspect on target device
Logging to External Service
Send logs to a remote service for debugging:
const sendToRemote = async (log: any) => {
if (process.env.NODE_ENV === 'development') {
await fetch('http://localhost:3000/logs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(log)
});
}
};
// Enhanced logger
export const logger = {
error: (...args: unknown[]) => {
console.error('[Chrome-Extension-Starter]', ...args);
sendToRemote({ level: 'error', message: args });
}
};
Troubleshooting Common Issues
Service Worker Inactive
Problem: Background service worker is inactive/sleeping
Solution:
- Click Inspect views: service worker to wake it up
- Add
chrome.runtime.onStartuplistener to log startup - Check if service worker terminates too quickly
Content Script Not Injecting
Problem: Content script doesn't run on certain pages
Debug:
// Check if content script loaded
console.log('[Content Script] Loaded on', window.location.href);
// Verify match patterns
logger.info('Match patterns', chrome.runtime.getManifest().content_scripts);
Check:
- URL matches
manifest.jsonpatterns - Page is not a restricted URL (chrome://, edge://)
Messages Not Received
Problem: Messages sent but not received
Debug:
// Sender
logger.info('[SENDER] Sending message', { type: MSG.CHANGE_BG, payload });
const result = await bus.sendToActive(MSG.CHANGE_BG, payload);
logger.info('[SENDER] Response received', result);
// Receiver
bus.on(MSG.CHANGE_BG, (payload, sender) => {
logger.info('[RECEIVER] Message received', { payload, sender });
return { ok: true };
});
Check:
- Listener is registered before sender executes
- Content script is injected
- No errors in console
Storage Not Persisting
Problem: Data not saved to Chrome Storage
Debug:
await kv.set('sync', 'theme', 'dark');
logger.info('Theme saved');
const theme = await kv.get('sync', 'theme');
logger.info('Theme retrieved', theme);
// Listen for changes
chrome.storage.onChanged.addListener((changes, area) => {
logger.info('Storage changed', { area, changes });
});
Check:
- Storage quota not exceeded
- Correct storage area (sync vs local)
storagepermission in manifest
Best Practices
- Use structured logging — Include context with every log
- Enable source maps — Debug TypeScript instead of compiled JS
- Log message flow — Track messages across contexts
- Check for errors — Always handle errors in async code
- Preserve logs — Enable "Preserve log" in DevTools
- Use descriptive labels — Make logs searchable