Project Structure
How Extro maps your filesystem to a Chrome extension.
Extro uses one rule: every entrypoint is a file under src/app/. The location of the file determines what kind of surface it becomes.
The tree
extro.config.ts
package.json
page.tsx
| Path | What it becomes |
|---|---|
src/app/popup/page.tsx | popup.html, the toolbar popup |
src/app/popup/settings/page.tsx | the #/settings route inside the popup |
src/app/popup/c/[id]/page.tsx | the dynamic #/c/:id route |
src/app/options/page.tsx | options.html, the options page |
src/app/sidepanel/page.tsx | sidepanel.html, Chrome's side panel |
src/app/content/index.ts | content.js, a content script |
src/app/background/index.ts | background.js, the MV3 service worker |
Anything outside src/app/ is just code: components, hooks, utils, libraries. Import freely.
Top-level directories
src/app/: surfaces and routes. The only directory with naming conventions. See Surfaces.icons/: extension icons by size (16.png,32.png,48.png,128.png). Whichever sizes you supply land inmanifest.iconsautomatically.public/: static files shipped to the output root with their names preserved. See Assets.extro.config.ts: identity, permissions, and manifest overrides. Everything is optional. See the config reference.
What ships
Running extro build produces an output/chrome-mv3-prod/ directory:
manifest.json
popup.html
popup.js
options.html
options.js
background.js
content.js
Entry files keep deterministic, unhashed names (popup.js, background.js) because the manifest references them by exact path. Shared chunks under assets/ are hashed as usual.
Good to know
Surfaces are optional. If your extension only needs a popup and a service worker, create only those two folders. The manifest reflects what you have, nothing more.