Chrome Extension Extension Cleanup Patterns — Best Practices
5 min readCleanup and Teardown Patterns for Chrome Extensions
Proper cleanup ensures extensions leave no trace when disabled, uninstalled, or updated. This document covers essential patterns for graceful shutdown.
Uninstall URL
Set an exit survey URL when users uninstall your extension:
// background.js
chrome.runtime.setUninstallURL('https://yourdomain.com/uninstall-survey');
Content Script Cleanup
Remove injected elements, event listeners, and observers when content scripts unload:
// content-script.js
class ContentScriptCleanup {
constructor() {
this.elements = [];
this.listeners = [];
this.observers = [];
}
addElement(el) { this.elements.push(el); }
addListener(fn, target = window) {
this.listeners.push({ fn, target });
}
addObserver(observer) { this.observers.push(observer); }
cleanup() {
// Remove injected DOM elements
this.elements.forEach(el => el.remove());
this.elements = [];
// Remove event listeners
this.listeners.forEach(({ fn, target }) => {
target.removeEventListener('click', fn);
});
this.listeners = [];
// Disconnect MutationObservers
this.observers.forEach(obs => obs.disconnect());
this.observers = [];
}
}
const cleanup = new ContentScriptCleanup();
// Example: injected element
const widget = document.createElement('div');
document.body.appendChild(widget);
cleanup.addElement(widget);
// Example: observer
const observer = new MutationObserver(() => {});
observer.observe(document.body, { childList: true });
cleanup.addObserver(observer);
// Cleanup on unload
window.addEventListener('unload', () => cleanup.cleanup());
Service Worker State Preservation
Save state before the service worker terminates:
// background.js
chrome.runtime.onSuspend.addListener(() => {
// Save current state to storage
chrome.storage.local.set({
lastState: getCurrentState(),
timestamp: Date.now()
});
});
Port Disconnect Handling
Clean up when communication ports are disconnected:
// background.js
const ports = new Set();
chrome.runtime.onConnect.addListener(port => {
ports.add(port);
port.onDisconnect.addListener(() => {
ports.delete(port);
// Perform cleanup for this port's context
});
});
Alarm Cleanup
Clear all alarms when a feature is disabled:
// background.js
function disableFeature(featureId) {
chrome.alarms.clearAll();
// Disable feature logic
}
Context Menu Cleanup
Remove all context menu items:
// background.js
chrome.contextMenus.removeAll(() => {
console.log('Context menus cleared');
});
Storage Migration Cleanup
Remove obsolete keys during version upgrades:
// background.js
chrome.runtime.onInstalled.addListener(details => {
if (details.reason === 'update') {
chrome.storage.local.get(['oldKey1', 'oldKey2'], items => {
if (items.oldKey1 !== undefined) {
chrome.storage.local.remove(['oldKey1', 'oldKey2']);
}
});
}
});
Tab Cleanup
Close extension-opened tabs on uninstall:
// background.js
chrome.runtime.onUninstalled.addListener(() => {
chrome.tabs.query({ url: '*://your-extension-tabs.com/*' }, tabs => {
tabs.forEach(tab => chrome.tabs.remove(tab.id));
});
});
CSS Cleanup
Remove dynamically injected styles:
// content-script.js
chrome.runtime.onMessage.addListener(msg => {
if (msg.action === 'cleanup') {
chrome.scripting.removeCSS({
css: '/* injected styles */',
target: { tabId: chrome.tab.id }
});
}
});
Memory Leak Prevention
Nullify references and clear intervals:
// Always clear intervals and nullify references
let intervalId = setInterval(doWork, 1000);
// On cleanup
clearInterval(intervalId);
intervalId = null;
someObject.reference = null;
Comprehensive Cleanup Manager
// cleanup-manager.js
class ExtensionCleanupManager {
constructor() {
this.cleanupTasks = [];
}
register(task) {
this.cleanupTasks.push(task);
}
async executeAll() {
for (const task of this.cleanupTasks) {
try {
await task();
} catch (e) {
console.error('Cleanup failed:', e);
}
}
this.cleanupTasks = [];
}
}
const manager = new ExtensionCleanupManager();
// Register cleanup tasks
manager.register(() => chrome.alarms.clearAll());
manager.register(() => chrome.contextMenus.removeAll());
manager.register(() => chrome.notifications.clear());
// On disable/uninstall
manager.executeAll();
See Also
Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.