Chrome Extension Crash Reporting — How to Monitor Errors and Fix Bugs Fast
10 min readChrome Extension Crash Reporting — How to Monitor Errors and Fix Bugs Fast
Introduction
Chrome extensions run in complex environments with multiple execution contexts: popup pages, background service workers, content scripts injected into web pages, and options pages. When an error occurs in any of these contexts, users rarely report it—unless you have a crash reporting system in place. Without proper error tracking, you’re essentially flying blind, unaware of the issues affecting your users.
Crash reporting helps you proactively identify and fix bugs before users abandon your extension. It provides visibility into runtime failures, uncaught exceptions, and unhandled promise rejections across all extension contexts. This guide covers implementing comprehensive crash reporting that captures errors from every part your extension.
Why Crash Reporting Matters for Extension Quality
Extensions face unique challenges that traditional web applications don’t encounter. Users install your extension across different Chrome versions, operating systems, and with various other extensions that might conflict with yours. Content scripts run in unpredictable web page environments, and service workers can terminate unexpectedly due to Chrome’s memory management.
A robust crash reporting system provides:
- Real-time error detection: Know about issues as they happen, not days later when a frustrated user leaves a negative review
- Context-rich diagnostics: Stack traces, browser version, operating system, and extension version help reproduce bugs
- Trend analysis: Identify which errors impact the most users and prioritize fixes accordingly
- User experience improvement: Quick bug fixes lead to higher ratings, better reviews, and increased user retention
The Chrome Web Store’s review process now actively considers crash rates and error handling. Extensions with poor error management risk visibility penalties.
Setting Up Global Error Handlers
The foundation of crash reporting starts with global error handlers that catch uncaught exceptions and unhandled promise rejections. Each extension context requires its own setup.
Background Service Worker and Popup Errors
In your background script or popup, add these handlers at the very top of your entry file:
// background.js or popup script
window.onerror = function(message, source, lineno, colno, error) {
const errorData = {
message: String(message),
source: source,
lineno: lineno,
colno: colno,
stack: error?.stack,
url: chrome.runtime?.getManifest()?.manifest_version,
timestamp: new Date().toISOString()
};
// Send to your error reporting service
reportError(errorData);
return false; // Allow default error handling
};
window.onunhandledrejection = function(event) {
const errorData = {
message: event.reason?.message || String(event.reason),
stack: event.reason?.stack,
type: 'unhandledRejection',
timestamp: new Date().toISOString()
};
reportError(errorData);
};
function reportError(errorData) {
// Queue errors and send in batches or immediately
console.error('[Crash Reporter]', errorData);
}
Content Script Error Handling
Content scripts run in the context of web pages, so you need to wrap your code in try-catch blocks and set up error handlers carefully:
// content-script.js
window.onerror = function(message, source, lineno, colno, error) {
// Only report errors that are likely from our extension
// Filter out page errors unless they're caused by our scripts
const isExtensionError = source?.includes('chrome-extension://');
if (isExtensionError) {
chrome.runtime.sendMessage({
type: 'CONTENT_SCRIPT_ERROR',
payload: {
message: String(message),
source: source,
lineno: lineno,
colno: colno,
stack: error?.stack,
url: window.location.href,
timestamp: new Date().toISOString()
}
});
}
return false;
};
Capturing Stack Traces from Content Scripts and Service Workers
Stack traces are invaluable for debugging. However, each extension context has different ways of capturing them.
Service Worker Stack Traces
Service workers in Manifest V3 can use the Error.prototype.stack property just like regular JavaScript. Chrome also provides the chrome.runtime.lastError that you should check after API calls:
// Service worker - check for runtime.lastError after every API call
chrome.storage.local.get(['key'], (result) => {
if (chrome.runtime.lastError) {
reportError({
type: 'runtime.lastError',
message: chrome.runtime.lastError.message,
context: 'storage.local.get',
stack: new Error().stack
});
}
});
Content Script Stack Traces from Injected Scripts
If your content script injects additional scripts, wrap them in error handlers:
function injectScript(scriptContent) {
const script = document.createElement('script');
script.textContent = `
(function() {
try {
${scriptContent}
} catch (e) {
window.postMessage({
type: 'EXTENSION_INJECTED_SCRIPT_ERROR',
error: {
message: e.message,
stack: e.stack
}
}, '*');
}
})();
`;
document.documentElement.appendChild(script);
script.remove();
}
// Listen for errors from injected scripts
window.addEventListener('message', (event) => {
if (event.data?.type === 'EXTENSION_INJECTED_SCRIPT_ERROR') {
reportError({
type: 'injectedScriptError',
...event.data.error,
url: window.location.href
});
}
});
Sending Error Reports to Your Backend
Rather than sending every error immediately (which can overwhelm your servers during error cascades), implement a queuing system that batches errors:
const errorQueue = [];
const MAX_QUEUE_SIZE = 10;
const FLUSH_INTERVAL = 5000; // 5 seconds
function reportError(errorData) {
errorQueue.push({
...errorData,
extensionVersion: chrome.runtime.getManifest().version,
userAgent: navigator.userAgent
});
if (errorQueue.length >= MAX_QUEUE_SIZE) {
flushErrors();
}
}
async function flushErrors() {
if (errorQueue.length === 0) return;
const errorsToSend = [...errorQueue];
errorQueue.length = 0;
try {
await fetch('https://your-api.com/crash-reports', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
errors: errorsToSend,
metadata: {
extensionId: chrome.runtime.id,
version: chrome.runtime.getManifest().version,
platform: navigator.platform
}
})
});
} catch (e) {
// Re-queue failed errors (with limit to prevent infinite loops)
if (errorQueue.length < 50) {
errorQueue.push(...errorsToSend);
}
}
}
setInterval(flushErrors, FLUSH_INTERVAL);
Using Sentry or Similar Services with Extensions
Sentry provides official SDKs that work well with extensions. For Manifest V3 service workers and popup pages, you can use the JavaScript SDK:
import * as Sentry from '@sentry/browser';
Sentry.init({
dsn: 'YOUR_SENTRY_DSN',
release: chrome.runtime.getManifest().version,
environment: process.env.NODE_ENV,
// Filter out irrelevant errors
ignoreErrors: [
/Extension context invalidated/,
/Message channel closed/,
/Could not establish connection/
],
// Capture unhandled promise rejections
captureUnhandledRejections: true
});
// Add user context if available
Sentry.setUser({
id: chrome.runtime.id
});
For content scripts, consider using a lightweight reporting approach that sends errors to the background script, which then forwards them to Sentry. This reduces the overhead in the page context:
// content-script - minimal error capture
window.onerror = function(...) {
chrome.runtime.sendMessage({
type: 'ERROR',
payload: { message, stack, ... }
});
return false;
};
// background script - Sentry integration
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'ERROR') {
Sentry.captureException(new Error(message.payload.message), {
extra: message.payload,
contexts: {
browser: {
url: sender.url,
tabId: sender.tab?.id
}
}
});
}
});
Analyzing Crash Trends and Prioritizing Fixes
Effective crash reporting isn’t just about collecting errors—it’s about using that data to drive improvements.
Key Metrics to Track
- Error frequency: How often does each error occur?
- User impact: What percentage of users experience each error?
- Error rate: Errors per user per day helps identify noisy errors
- Resolution time: How quickly do you fix reported errors?
Prioritization Framework
Rank errors using this formula: Priority = (Error Rate) × (User Impact) × (Severity)
- Critical: Crashes the extension entirely, data loss
- High: Major feature broken, affects core functionality
- Medium: Minor feature broken, workarounds available
- Low: UI glitches, non-essential features
Creating Actionable Reports
Configure your error tracking tool to group similar errors automatically. Sentry’s fingerprinting helps:
Sentry.configureScope((scope) => {
scope.setFingerprint([
'{{ default }}',
errorData.type,
errorData.context
]);
});
Review crash reports weekly. Look for patterns: errors that spike after a new release indicate regression bugs. Errors in specific browser versions may reveal compatibility issues.
Implementation Checklist
- Add global error handlers to background service worker
- Add error handlers to popup and options pages
- Implement content script error capturing
- Set up error queuing and batching
- Integrate with Sentry or build custom backend
- Add user consent and privacy notice in extension
- Test error reporting in development
- Create dashboards for error monitoring
- Establish weekly crash report review process
Conclusion
Implementing comprehensive crash reporting across your Chrome extension’s various contexts gives you visibility into real-world issues affecting your users. Start with basic error handlers, integrate a service like Sentry for richer diagnostics, and establish regular review processes to turn crash data into actionable fixes. The investment pays dividends through improved user satisfaction, better reviews, and a more stable extension.
Related Articles
- Feature Flags - Kill switches for emergency responses
- Extension Feature Flags Implementation - Disable problematic features
- Remote Config - Update settings without republishing
- A/B Testing - Monitor experiment errors
- Extension Onboarding - Track onboarding errors
- Extension Logging - Structured logging patterns
- Storage API - Error queuing and persistence