# Browser launch flags for Chrome, Firefox, and Edge Source: https://extension.js.org/docs/browsers/browser-flags Use firefox://flags, chrome://flags, and browser launch flags in Extension.js. Control Chrome, Firefox, and Edge runtime behavior during extension development. Control browser launch behavior for debugging, automation, and runtime experiments. **Looking for `chrome://flags` or `edge://flags`?** Type it into your address bar, which is the browser's built-in page. **Firefox has no `firefox://flags`**; use `about:config` instead. If you're toggling flags to **test or build a browser extension**, Extension.js applies them per-project automatically across Chrome, Edge, and Firefox, so you don't pass flags by hand on every run. [Create your first extension in 30 seconds →](/docs/getting-started) Tune browser launch behavior without changing extension source code. Extension.js merges browser flags from your `extension.config.*` and applies them in `dev`, `preview`, and `start` flows. ## Does Firefox have `firefox://flags`? Firefox does not use `firefox://flags` the way Chromium browsers use `chrome://flags` or `edge://flags`. Firefox exposes runtime toggles through `about:config` (preferences) and accepts launch flags from the command line. If you searched for `firefox://flags`, `firefox //flags`, `browser://flags`, `mozilla://flags`, or `about flags firefox`, you are probably trying to change browser behavior during extension development. In Extension.js, do that in two places: * Use [`browserFlags`](/docs/browsers/browser-flags) for launch-time flags Extension.js passes to the browser binary. * Use [Firefox preferences](/docs/browsers/browser-preferences) for repeatable Gecko runtime behavior that would otherwise live in `about:config`. Both work for Chrome, Edge, and Firefox extension development from the same Extension.js project. ## Flags in other browsers Extension.js launches and configures any Chromium-based browser the same way. * **`brave://flags`, `opera://flags`, `vivaldi://flags`, `yandex://flags`**: all Chromium-based, so they behave identically to `chrome://flags`, and Extension.js manages them per-project. * **`internet://flags` and `browser://flags`**: these are not real browser schemes. You are most likely looking for `chrome://flags` (Chromium) or `about:config` (Firefox). * **`edge://flags`**: Edge is Chromium, so it works exactly like Chrome in Extension.js. Building an extension that needs specific flags at launch? Set them once in `extension.config.*` and Extension.js applies them every run. See [`browserFlags`](/docs/browsers/browser-flags) below. ## Template examples ### `new-browser-flags` new-browser-flags template screenshot See browser flags in action with a new-tab extension that configures launch behavior. ```bash npm theme={null} npx extension@latest create my-extension --template=newtab-browser-flags ``` ```bash pnpm theme={null} pnpx extension@latest create my-extension --template=newtab-browser-flags ``` ```bash yarn theme={null} yarn dlx extension@latest create my-extension --template=newtab-browser-flags ``` ```bash bun theme={null} bunx extension@latest create my-extension --template=newtab-browser-flags ``` ```bash deno theme={null} deno run -A npm:extension@latest create my-extension --template=newtab-browser-flags ``` Repository: [extension-js/examples/newtab-browser-flags](https://github.com/extension-js/examples/tree/main/examples/newtab-browser-flags) ## How it works Configure flags in `extension.config.*`: * `browser..browserFlags` * `commands.dev|start|preview.browserFlags` * Optional `excludeBrowserFlags` to remove defaults or user flags (behavior depends on whether you target Chromium or Firefox). Override order: browser defaults → command defaults → CLI-selected command context. ## Flag capabilities | Config key | What it does | | -------------------------------------- | -------------------------------------------------------- | | `browser..browserFlags` | Sets default launch flags for a specific browser target. | | `commands.dev.browserFlags` | Adds or overrides flags for `dev` runs. | | `commands.start.browserFlags` | Adds or overrides flags for `start` runs. | | `commands.preview.browserFlags` | Adds or overrides flags for `preview` runs. | | `browser..excludeBrowserFlags` | Removes matching default or user flags for a target. | | `commands..excludeBrowserFlags` | Removes flags in a command-specific context. | ``` ```ts newtab.ts theme={null} const frame = document.getElementById("sandbox") as HTMLIFrameElement; frame.addEventListener("load", () => { frame.contentWindow?.postMessage({ template: "Hello {{name}}" }, "*"); }); window.addEventListener("message", (event) => { console.log("rendered:", event.data.html); }); ``` ```ts sandbox.ts theme={null} window.addEventListener("message", (event) => { const render = new Function("name", `return \`${event.data.template}\`;`); event.source?.postMessage({ html: render("world") }, { targetOrigin: "*" }); }); ``` The embedding page keeps its `chrome.*` access and acts as the broker: it reads storage, calls APIs, and passes plain data in and out of the sandbox. ## Not the same thing: `background.type` A common mix-up: `"background": {"type": "module"}` has nothing to do with sandboxing. It declares the background service worker as an ES module so `import` statements work at registration. Extension.js sets it automatically when your background entry uses ESM syntax, so you rarely write it by hand. If your confusion was about `import` failing in the service worker, see [Background scripts](/docs/implementation-guide/background); if it was about running dynamic code, you are in the right place. ## Custom sandbox CSP You can loosen or tighten the sandbox CSP further with `content_security_policy.sandbox` in the manifest. Keep `script-src` as narrow as the feature allows; the sandbox exists to contain dynamic code, not to disable security review. The [security checklist](/docs/workflows/security-checklist) covers what reviewers look for. ## See also * [HTML entrypoints](/docs/implementation-guide/html) * [Background scripts](/docs/implementation-guide/background) * [Messaging](/docs/implementation-guide/messaging) * [Security checklist](/docs/workflows/security-checklist) # Shadow DOM in content scripts Source: https://extension.js.org/docs/implementation-guide/shadow-dom Mount content script UI inside a shadow root so page CSS cannot reach it. Extension.js recognizes the host element, hydrates bundle CSS, and cleans up on reload. A content script shares the page's document. Page CSS reaches your markup, and your CSS reaches the page. A shadow root ends both directions. Extension.js does not create the shadow root for you. You create it, and the toolchain recognizes the host that you created. This page covers the contract between the two. ## The pattern Every content template uses the same shape: ```js src/content/scripts.js theme={null} export default function initial() { const rootDiv = document.createElement("div"); rootDiv.setAttribute("data-extension-root", "true"); rootDiv.style.cssText = "all: initial !important"; document.body.appendChild(rootDiv); const shadowRoot = rootDiv.attachShadow({ mode: "open" }); const contentDiv = document.createElement("div"); contentDiv.className = "content_script"; shadowRoot.appendChild(contentDiv); return () => { rootDiv.remove(); }; } ``` Three parts carry weight: * **`data-extension-root`** marks the host so Extension.js can find it. * **`all: initial !important`** protects the host element itself. A shadow root shields its descendants, not the host. Without that line, a page rule such as `div { opacity: .8 }` fades the widget. * **The returned function** removes the host. Extension.js calls it before it mounts the next version. ## The host element that Extension.js looks for Extension.js finds your host with one selector: ```plaintext theme={null} #extension-root, [data-extension-root] ``` Use the `data-extension-root` attribute or the `extension-root` id. There is no class-based form. The value of the attribute is free, so `"true"` is a convention, not a requirement. The value `extension-js-devtools` is reserved for the companion extension Extension.js loads during development, and the selector excludes it so the two never adopt each other's roots. While you develop, Extension.js stamps bookkeeping attributes onto your host: | Attribute | What it records | | ---------------------------- | -------------------------------------------------------- | | `data-extjs-reinject-owner` | The script that owns the host, qualified by extension id | | `data-extjs-reinject-key` | The entry that mounted it | | `data-extjs-reinject-build` | The build that mounted it | | `data-extjs-reinject-status` | `mounted`, `executed`, `cleaned`, or `mount-error` | Those attributes are removed from production builds. Do not write selectors against them. ## Getting CSS into the shadow root A shadow root ignores stylesheets that live outside it. That single rule explains every case below. ### CSS that you import into the script Import a stylesheet from a content script and Extension.js inlines it as a `data:` URL rather than emitting a link. Fetch it and put the text into a `