wxt-dev/wxt

Support new content script registration `optional`

Open

#2239 opened on Apr 10, 2026

View on GitHub
 (5 comments) (0 reactions) (0 assignees)TypeScript (9,861 stars) (511 forks)user submission
contribution welcomegood first issue

Description

Feature Request

Add a new registration: 'optional' option for content scripts that moves their matches into optional_host_permissions instead of host_permissions, and registers them dynamically via browser.scripting.registerContentScripts() at runtime.

Currently, registration: 'runtime' moves matches to host_permissions, which still triggers Chrome's permission escalation dialog when new hosts are added on extension update (disabling the extension until the user re-accepts). Extensions that want to support new hosts without disabling need to use optional_host_permissions instead.

Proposed API:

export default defineContentScript({
    matches: ['https://my-app.com/*'],
    registration: 'optional',
    // ...
});

Behavior:

  • Strip the content script's matches from manifest.json content_scripts
  • Add them to optional_host_permissions instead of host_permissions
    • Would be nice if this was deduped if user already had urls in optional_host_permissions. example if I had *.app.com in optional_host_permissions theres no need to put auth.app.com because its covered by the existing one
  • Nice to have: Register dynamically via browser.scripting.registerContentScripts() in the background script
  • Scripts run only after the user grants the optional host permission

Is your feature request related to a bug?

N/A — though this addresses a common pain point where adding new content_scripts.matches entries causes Chrome to disable the extension on update due to permission escalation, even when the hosts are already covered by optional_host_permissions.

What are the alternatives?

We built a custom WXT module that:

  1. Hooks into entrypoints:resolved to identify content scripts targeting optional hosts
  2. Generates a virtual module with the optional content script data via prepare:types
  3. Strips them from the manifest in build:manifestGenerated
  4. Registers them dynamically in the background script using browser.scripting.registerContentScripts()

This works but requires significant custom infrastructure that could be a first-class WXT feature. The registration: 'runtime' codepath already handles most of this — the main difference is the target permission type (optional_host_permissions vs host_permissions).

Additional context

  • Chrome treats any new entry in content_scripts.matches as a permission escalation, regardless of whether the host is already in optional_host_permissions
  • optional_host_permissions changes alone do not trigger the escalation dialog
  • browser.scripting.registerContentScripts() registrations persist across service worker restarts and extension updates, so they only need to be set up once
  • This is a common pattern for extensions that progressively support new domains without disrupting existing users

Contributor guide