manifest.json and wraps them for runtime mounting and hot module replacement (HMR) behavior. It emits predictable content_scripts/* outputs.
Content script capabilities
| Capability | What it gives you |
|---|---|
| Manifest-driven entries | Compile JS/CSS content script lists directly from manifest |
| dev remount flow | Update scripts/styles quickly through wrapper-driven behavior |
| MAIN vs isolated world support | Use MAIN world (page JavaScript context) or isolated world (sandboxed extension context) |
| Predictable output layout | Emit normalized content_scripts/* artifacts |
Template examples
content

content-react

Supported manifest fields
| Manifest field | File type expected |
|---|---|
content_scripts.js | .js, .jsx, .ts, .tsx, .mjs |
content_scripts.css | .css, .scss, .sass, .less |
Sample content script declaration
Example content script declaration inmanifest.json:
Authoring contract
For every content-script-like entry, Extension.js expects a mount-style default export. This is a function that sets up behavior and optionally returns a cleanup callback:- The module should
export defaulta synchronous function. - That function should perform setup work.
- It may return a synchronous cleanup callback.
- Extension.js does not support classes as the default export.
- files referenced by
manifest.json > content_scripts[*].js - files you place under the project
scripts/folder and use as script entrypoints
Valid shapes
Invalid shapes
Guidance for async content scripts
Keep the default export synchronous even when the feature does async work internally. Start async work inside the function and return a synchronous cleanup. Why this matters: Extension.js remounts content scripts during development. Without a cleanup function, you can easily duplicate UI, event listeners, observers, and timers.What happens on contract violations
- No default export: Extension.js warns during development and skips mounting.
- Default export is not a function: Extension.js warns during development and skips mounting.
- Default export returns a Promise: the module still runs, but Extension.js does not treat that
Promiseas cleanup.
Runtime wrapper behavior
- Extension.js wraps content script modules with mount/runtime helpers.
- In development mode, Extension.js adds HMR accept/dispose behavior and remount flow.
- CSS updates trigger remount events (
__EXTENSIONJS_CSS_UPDATE__) in development. - Extension.js respects
run_attiming from manifest values.
Multi-entry content scripts
You can declare multiple content script entries in a single manifest. Each entry compiles independently with its own match patterns, run timing, and world settings.content-multi-one-entry

content_scripts manifest entry.
content-multi-three-entries

content_scripts manifest entries with independent match patterns.
scripts/ folder behavior
The scripts/ folder is for script entrypoints that no HTML page entry declares. In practice, these entries follow the same default-export pattern as content scripts.
That means scripts/ is not just a generic folder for loose JavaScript files:
- Script entry files should still have a default export function when they are meant to mount behavior
- Extension.js treats adding or removing supported files under
scripts/as a structural change in watch mode - Extension.js may require a dev server restart when that entry set changes
Output path
Extension.js normalizes content script entries per manifest index:content-0.abcd1234.js). This forces the browser to load a fresh chrome-extension:// URL after each rebuild.
Chrome aggressively caches extension resources, so the hash prevents stale code. Production builds use clean content-0.js names.
MAIN world notes
content-main-world
See MAIN world content scripts in action with a working example that injects UI directly into the page context:
world: "MAIN"is Chromium-only. Firefox does not support theworldfield and ignores it. Your script still runs in the isolated world on Firefox.- Cross-browser MAIN world behavior: Use the
chromium:manifest prefix to declare it only for Chromium targets. Then provide an isolated-world fallback for Firefox. - In development, MAIN-world scripts use an internal bridge mechanism to load code chunks and resolve public paths.
- Extension APIs (
chrome.runtime,chrome.storage, etc.) are not available in the MAIN world — you can only access page-context globals. - Treat MAIN world as an advanced path. Validate behavior on each target browser early.
Isolated vs MAIN quick example
MAIN only when you need page-context access, and account for extension API/runtime constraints.
Cross-browser MAIN world pattern
Use browser-specific prefixes to declare MAIN world only for Chromium and provide an isolated fallback for Firefox:chromium: prefixed fields entirely, so only Chromium targets get the MAIN-world script.
Matching and execution guidance
The browser still controls where a content script runs. Extension.js bundles the file, but the manifest entry still defines where and when the script runs.- Keep
matchesas narrow as the feature allows. - Add
exclude_matches,all_frames, ormatch_about_blankonly when the feature actually requires those behaviors. - Treat
run_atandworldas part of the feature contract, not just implementation detail. - Re-test permission and host-permission scope when changing where a content script runs.
Development behavior
- Editing content script code usually updates through wrapper-driven HMR/remount flow.
- CSS-only entries receive dev helper behavior so style updates can propagate.
- If content script entrypoint lists change in manifest, Extension.js may require a dev server restart.
Best practices
- Keep content script entry files small and delegate logic to shared modules.
- Scope selectors/styles carefully to avoid host-page collisions.
- Prefer explicit
run_atandworldvalues when behavior depends on timing/context. - Treat manifest content-script list changes as structural development changes.
- Pass page-derived data through validated messaging instead of performing privileged work directly in the content script.
- Default to isolated world and move to
MAINonly when the page context is strictly required.
Next steps
- Understand update outcomes in dev update behavior.
- Design cross-context communication with Messaging.
- Review access scope in Permissions and host permissions.
- Learn about web-accessible resources.
- Continue with locales in development.

