Chrome Extension Conflicts: Who Wins and 5 Code Fixes
Originally published on ExtensionBooster. Chrome extension conflicts are behind a lot of "Doesn't work. Uninstalled." reviews that you can never reproduce on your own machine. Another extension is touching the same page
Originally published on ExtensionBooster.
Chrome extension conflicts are behind a lot of "Doesn't work. Uninstalled." reviews that you can never reproduce on your own machine. Another extension is touching the same page, request or shortcut, and yours is the one the user sees failing. Here is where extensions collide, which one Chrome lets win, and five small code changes that make yours hold up.
Isolated worlds don't mean an isolated page
Content scripts get their own JavaScript world, so your variables never clash with another extension's. The DOM is still shared. Every extension and the page itself read and write the same elements, styles and events. Inject with world: "MAIN" and you also share the page's JavaScript (and its CSP), which is where two extensions patching fetch or history.pushState step on each other.
This matters more now: Google's August 2026 Chrome Web Store update says ratings will lean on more recent reviews, so a short burst of conflict-driven one-stars costs more than it used to.
Who wins when extensions collide
| Shared surface | Typical symptom | Who wins |
|---|---|---|
| Page DOM and CSS | Your UI restyled, duplicated or hidden behind a z-index | Last to run or strongest selector |
| MAIN-world JavaScript | Patched globals break | Last to patch |
| declarativeNetRequest | A request your feature needs is blocked | block > redirect/upgradeScheme > allow; same type goes to the most recently installed extension |
| webRequest (policy-installed only in MV3) | Your change is silently ignored | Most recently installed; onActionIgnored tells you |
| Keyboard shortcuts | Your shortcut never registers | Whoever registered it first |
| Proxy and similar settings | Your setting doesn't apply | Extension with higher precedence |
The DNR row is the one that surprises people: your allow rule cannot override another extension's block.
Five fixes worth shipping
1. Mount injected UI in a shadow root. Outside CSS stays out, your styles stay in.
const HOST = 'myext-panel-host';
if (!document.getElementById(HOST)) {
const host = Object.assign(document.createElement('div'), { id: HOST });
const shadow = host.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
<style>:host { all: initial; } .box { position: fixed; bottom: 16px; right: 16px; }</style>
<div class="box">Notes</div>`;
document.documentElement.appendChild(host);
}
Prefix every ID, class and data attribute you add (data-myext-*), skip global CSS, and don't chase the biggest possible z-index.
2. Make DOM edits idempotent. Two MutationObservers can keep triggering each other. Mark nodes you've handled and skip them:
const DONE = 'data-myext-done';
const handle = (el) => {
if (el.hasAttribute(DONE)) return;
el.setAttribute(DONE, '');
// add your button or badge
};
Also stay away from stopPropagation() on document-level listeners unless you really mean to swallow the event for everyone.
3. Give every DNR rule an explicit priority. Browser vendors agreed not to standardize ordering for rules with the same action and priority, so set "priority" whenever order matters. Across extensions, plan for blocks: serve from your own domain, avoid ad-looking paths like /ads/, /track or /pixel, show a clear "a network filter may be blocking this" message, and use declarativeNetRequestFeedback in unpacked builds to see which of your rules matched.
4. Check shortcuts on install.
chrome.runtime.onInstalled.addListener(async ({ reason }) => {
if (reason !== 'install') return;
const unassigned = (await chrome.commands.getAll()).filter((c) => !c.shortcut);
if (unassigned.length) {
// tell the user on your welcome page, link chrome://extensions/shortcuts
}
});
Only run it on install, because later the user may have cleared a shortcut on purpose.
5. Read levelOfControl for settings. If chrome.proxy.settings.get({}) returns controlled_by_other_extensions, say so plainly instead of letting the user write "VPN won't connect."
Should you use chrome.management to list the other extensions? It adds the "Manage your apps, extensions, and themes" install warning. For most extensions, detecting the symptom (restyled element, failed request, empty shortcut) is the better trade.
Reproduce it before your users do
Build a small test matrix from the extensions your users actually run next to yours. Ad blockers, password managers, grammar checkers and dark mode tools are common suspects. Since Chrome 137, branded Chrome ignores --load-extension, but Chromium and Chrome for Testing (Puppeteer's default download) still accept it:
const browser = await puppeteer.launch({
headless: false,
args: [
`--disable-extensions-except=${mine},${suspect}`,
`--load-extension=${mine},${suspect}`,
],
});
When a user reports something you can't reproduce, send a two-minute test:
- Turn on "Allow in Incognito" for your extension in
chrome://extensions. - Retry in an Incognito window.
- If it works there, re-enable their other extensions one at a time until it breaks again.
When the same culprit shows up three times, add it to your matrix and to a "Known conflicts" section on your help page. Then reply to the review, ship the fix, and edit the reply to name the version that solves it.
Key takeaways
- Content scripts have isolated JavaScript, but the DOM, network, shortcuts and some settings are shared.
- In declarativeNetRequest, block beats redirect beats allow across extensions; ties go to the most recently installed one.
- Shadow roots, prefixed names and idempotent DOM edits prevent most visible conflicts.
- Check
chrome.commands.getAll()on install andlevelOfControlfor settings, then tell the user what to do. - A two-minute Incognito test turns "doesn't work" into a diagnosable bug report.
The original post goes deeper on review replies and support workflows in the full guide to fixing Chrome extension conflicts with other extensions, and ExtensionBooster's growth and review tools for extension developers cover what comes after the fix.
Which extension has broken yours most often in the wild, and how did you finally track it down?
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes โ full credit and traffic to the original publisher.