MV3 dev dynamic content script registration drops matchOriginAsFallback
#2,335 opened on May 4, 2026
Repository metrics
- Stars
- (9,861 stars)
- PR merge metrics
- (Avg merge 11h 6m) (11 merged PRs in 30d)
Description
Describe the bug
In MV3 development mode, WXT dynamically registers content scripts from the background service worker instead of putting them in manifest.content_scripts.
When a content script is defined with matchOriginAsFallback: true, the production manifest preserves the option as match_origin_as_fallback: true, but the dev-mode dynamic registration drops it. As a result, dev mode behaves differently from production for about:blank / about:srcdoc iframes that depend on origin fallback.
Reproduction
Define a content script like this:
export default defineContentScript({
matches: ["*://*/*", "file:///*"],
allFrames: true,
matchAboutBlank: true,
matchOriginAsFallback: true,
async main() {
// ...
},
})
Run WXT in MV3 dev mode:
wxt -b edge
Then inspect the registered content scripts from the extension service worker:
await chrome.scripting.getRegisteredContentScripts()
Actual behavior
The dynamically registered content script has matchOriginAsFallback: false:
{
"id": "wxt:content-scripts/node-trigger.js",
"js": ["content-scripts/node-trigger.js"],
"matches": ["*://*/*", "file:///*"],
"allFrames": true,
"matchOriginAsFallback": false,
"runAt": "document_idle",
"world": "ISOLATED"
}
This prevents the content script from running in a TinyMCE editor iframe whose document is about:srcdoc, even though that same script works in production.
Expected behavior
The dev-mode dynamic registration should preserve matchOriginAsFallback: true:
{
"id": "wxt:content-scripts/node-trigger.js",
"js": ["content-scripts/node-trigger.js"],
"matches": ["*://*/*", "file:///*"],
"allFrames": true,
"matchOriginAsFallback": true,
"runAt": "document_idle",
"world": "ISOLATED"
}
Production build already emits the expected static manifest fields:
{
"matches": ["*://*/*", "file:///*"],
"all_frames": true,
"match_about_blank": true,
"js": ["content-scripts/node-trigger.js"],
"match_origin_as_fallback": true
}
Likely cause
In mapWxtOptionsToContentScript, WXT maps:
match_origin_as_fallback: options.matchOriginAsFallback
But mapWxtOptionsToRegisteredContentScript does not map the equivalent dynamic registration field:
function mapWxtOptionsToRegisteredContentScript(options, js, css) {
return {
allFrames: options.allFrames,
excludeMatches: options.excludeMatches,
matches: options.matches,
runAt: options.runAt,
js,
css,
world: options.world
};
}
Adding matchOriginAsFallback: options.matchOriginAsFallback to the dynamic registration payload fixes the dev-mode behavior in my testing.
Notes
In my Edge MV3 dev testing, chrome.scripting.registerContentScripts accepted matchOriginAsFallback, and that was enough for the about:srcdoc iframe case. Passing matchAboutBlank to dynamic registration appeared to make registration fail, so this report is specifically about preserving matchOriginAsFallback for dev-mode dynamic content script registration.
Environment
- WXT:
0.20.25 - Browser target:
edge-mv3 - Mode: development (
wxt -b edge) *** End Patch