Entrypoints
WXT uses the files inside the entrypoints/
directory as inputs when bundling your extension. They can be HTML, JS, CSS, or any variant of those file types supported by Vite (TS, JSX, SCSS, etc).
Folder Structure
Inside the entrypoints/
directory, an entrypoint is defined as a single file or directory (with an index
file) inside it.
📂 entrypoints/
📄 {name}.{ext}
📂 entrypoints/
📂 {name}/
📄 index.{ext}
The entrypoint's name
dictates the type of entrypoint. For example, to add a "Background" entrypoint, either of these files would work:
📂 entrypoints/
📄 background.ts
📂 entrypoints/
📂 background/
📄 index.ts
Refer to the Entrypoint Types section for the full list of listed entrypoints and their filename patterns.
Including Other Files
When using an entrypoint directory, entrypoints/{name}/index.{ext}
, you can add related files next to the index
file.
📂 entrypoints/
📂 popup/
📄 index.html ← This file is the entrypoint
📄 main.ts
📄 style.css
📂 background/
📄 index.ts ← This file is the entrypoint
📄 alarms.ts
📄 messaging.ts
📂 youtube.content/
📄 index.ts ← This file is the entrypoint
📄 style.css
DANGER
DO NOT put files related to an entrypoint directly inside the entrypoints/
directory. WXT will treat them as entrypoints and try to build them, usually resulting in an error.
Instead, use a directory for that entrypoint:
📂 entrypoints/
📄 popup.html
📄 popup.ts
📄 popup.css
📂 popup/
📄 index.html
📄 main.ts
📄 style.css
Deeply Nested Entrypoints
While the entrypoints/
directory might resemble the pages/
directory of other web frameworks, like Nuxt or Next.js, it does not support deeply nesting entrypoints in the same way.
Entrypoints must be zero or one levels deep for WXT to discover and build them:
📂 entrypoints/
📂 youtube/
📂 content/
📄 index.ts
📄 ...
📂 injected/
📄 index.ts
📄 ...
📂 youtube.content/
📄 index.ts
📄 ...
📂 youtube-injected/
📄 index.ts
📄 ...
Unlisted Entrypoints
In web extensions, there are two types of entrypoints:
- Listed: Referenced in the
manifest.json
- Unlisted: Not referenced in the
manifest.json
Throughout the rest of WXT's documentation, listed entrypoints are referred to by name. For example:
- Popup
- Options
- Background
- Content Script
However, not all entrypoints in web extensions are listed in the manifest. Some are not listed in the manifest, but are still used by extensions. For example:
- A welcome page shown in a new tab when the extension is installed
- JS files injected by content scripts into the main world
For more details on how to add unlisted entrypoints, see:
Defining Manifest Options
Most listed entrypoints have options that need to be added to the manifest.json
. However with WXT, instead of defining the options in a separate file, you define these options inside the entrypoint file itself.
For example, here's how to define matches
for content scripts:
export default defineContentScript({
matches: ['*://*.wxt.dev/*'],
main() {
// ...
},
});
For HTML entrypoints, options are configured as <meta>
tags. For example, to use a page_action
for your MV2 popup:
<!doctype html>
<html lang="en">
<head>
<meta name="manifest.type" content="page_action" />
</head>
</html>
Refer to the Entrypoint Types sections for a list of options configurable inside each entrypoint, and how to define them.
When building your extension, WXT will look at the options defined in your entrypoints, and generate the manifest accordingly.
Entrypoint Types
Background
Filename | Output Path | |
---|---|---|
entrypoints/background.[jt]s | /background.js | |
entrypoints/background/index.[jt]s | /background.js |
export default defineBackground(() => {
// Executed when background is loaded
});
export default defineBackground({
// Set manifest options
persistent: undefined | true | false,
type: undefined | 'module',
// Set include/exclude if the background should be removed from some builds
include: undefined | string[],
exclude: undefined | string[],
main() {
// Executed when background is loaded, CANNOT BE ASYNC
},
});
For MV2, the background is added as a script to the background page. For MV3, the background becomes a service worker.
When defining your background entrypoint, keep in mind that WXT will import this file in a NodeJS environment during the build process. That means you cannot place any runtime code outside the main
function.
browser.action.onClick.addListener(() => {
// ...
});
export default defineBackground(() => {
browser.action.onClick.addListener(() => {
// ...
});
});
Refer to the Entrypoint Loaders documentation for more details.
Bookmarks
Filename | Output Path | |
---|---|---|
entrypoints/bookmarks.html | /bookmarks.html | |
entrypoints/bookmarks/index.html | /bookmarks.html |
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
</head>
<body>
<!-- ... -->
</body>
</html>
When you define a Bookmarks entrypoint, WXT will automatically update the manifest to override the browser's bookmarks page with your own HTML page.
Content Scripts
Filename | Output Path | |
---|---|---|
entrypoints/content.[jt]sx? | /content-scripts/content.js | |
entrypoints/content/index.[jt]sx? | /content-scripts/content.js | |
entrypoints/{name}.content.[jt]sx? | /content-scripts/{name}.js | |
entrypoints/{name}.content/index.[jt]sx? | /content-scripts/{name}.js |
export default defineContentScript({
// Set manifest options
matches: string[],
excludeMatches: undefined | [],
includeGlobs: undefined | [],
excludeGlobs: undefined | [],
allFrames: undefined | true | false,
runAt: undefined | 'document_start' | 'document_end' | 'document_idle',
matchAboutBlank: undefined | true | false,
matchOriginAsFallback: undefined | true | false,
world: undefined | 'ISOLATED' | 'MAIN',
// Set include/exclude if the background should be removed from some builds
include: undefined | string[],
exclude: undefined | string[],
// Configure how CSS is injected onto the page
cssInjectionMode: undefined | "manifest" | "manual" | "ui",
// Configure how/when content script will be registered
registration: undefined | "manifest" | "runtime",
main(ctx: ContentScriptContext) {
// Executed when content script is loaded, can be async
},
});
When defining content script entrypoints, keep in mind that WXT will import this file in a NodeJS environment during the build process. That means you cannot place any runtime code outside the main
function.
browser.runtime.onMessage.addListener((message) => {
// ...
});
export default defineBackground(() => {
browser.runtime.onMessage.addListener((message) => {
// ...
});
});
Refer to the Entrypoint Loaders documentation for more details.
See Content Script UI for more info on creating UIs and including CSS in content scripts.
Devtools
Filename | Output Path | |
---|---|---|
entrypoints/devtools.html | /devtools.html | |
entrypoints/devtools/index.html | /devtools.html |
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
</head>
<body>
<!-- ... -->
</body>
</html>
Follow the Devtools Example to add different panels and panes.
History
Filename | Output Path | |
---|---|---|
entrypoints/history.html | /history.html | |
entrypoints/history/index.html | /history.html |
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
</head>
<body>
<!-- ... -->
</body>
</html>
When you define a History entrypoint, WXT will automatically update the manifest to override the browser's history page with your own HTML page.
Newtab
Filename | Output Path | |
---|---|---|
entrypoints/newtab.html | /newtab.html | |
entrypoints/newtab/index.html | /newtab.html |
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
</head>
<body>
<!-- ... -->
</body>
</html>
When you define a Newtab entrypoint, WXT will automatically update the manifest to override the browser's new tab page with your own HTML page.
Options
Filename | Output Path | |
---|---|---|
entrypoints/options.html | /options.html | |
entrypoints/options/index.html | /options.html |
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Options Title</title>
<!-- Customize the manifest options -->
<meta name="manifest.open_in_tab" content="true|false" />
<meta name="manifest.chrome_style" content="true|false" />
<meta name="manifest.browser_style" content="true|false" />
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
</head>
<body>
<!-- ... -->
</body>
</html>
Popup
Filename | Output Path | |
---|---|---|
entrypoints/popup.html | /popup.html | |
entrypoints/popup/index.html | /popup.html |
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Set the `action.default_title` in the manifest -->
<title>Default Popup Title</title>
<!-- Customize the manifest options -->
<meta
name="manifest.default_icon"
content="{
16: '/icon-16.png',
24: '/icon-24.png',
...
}"
/>
<meta name="manifest.type" content="page_action|browser_action" />
<meta name="manifest.browser_style" content="true|false" />
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
</head>
<body>
<!-- ... -->
</body>
</html>
Sandbox
Chromium Only
Firefox does not support sandboxed pages.
Filename | Output Path | |
---|---|---|
entrypoints/sandbox.html | /sandbox.html | |
entrypoints/sandbox/index.html | /sandbox.html | |
entrypoints/{name}.sandbox.html | /{name}.html | |
entrypoints/{name}.sandbox/index.html | /{name}.html |
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
</head>
<body>
<!-- ... -->
</body>
</html>
Side Panel
Filename | Output Path | |
---|---|---|
entrypoints/sidepanel.html | /sidepanel.html | |
entrypoints/sidepanel/index.html | /sidepanel.html | |
entrypoints/{name}.sidepanel.html | /{name}.html` | |
entrypoints/{name}.sidepanel/index.html | /{name}.html` |
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Default Side Panel Title</title>
<!-- Customize the manifest options -->
<meta
name="manifest.default_icon"
content="{
16: '/icon-16.png',
24: '/icon-24.png',
...
}"
/>
<meta name="manifest.open_at_install" content="true|false" />
<meta name="manifest.browser_style" content="true|false" />
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
</head>
<body>
<!-- ... -->
</body>
</html>
In Chrome, side panels use the side_panel
API, while Firefox uses the sidebar_action
API.
Unlisted CSS
Filename | Output Path | |
---|---|---|
entrypoints/{name}.(css|scss|sass|less|styl|stylus) | /{name}.css | |
entrypoints/{name}/index.(css|scss|sass|less|styl|stylus) | /{name}.css | |
entrypoints/content.(css|scss|sass|less|styl|stylus) | /content-scripts/content.css | |
entrypoints/content/index.(css|scss|sass|less|styl|stylus) | /content-scripts/content.css | |
entrypoints/{name}.content.(css|scss|sass|less|styl|stylus) | /content-scripts/{name}.css | |
entrypoints/{name}.content/index.(css|scss|sass|less|styl|stylus) | /content-scripts/{name}.css |
body {
/* ... */
}
Follow Vite's guide to setup your preprocessor of choice: https://vitejs.dev/guide/features.html#css-pre-processors
CSS entrypoints are always unlisted. To add CSS to a content script, see the Content Script docs.
Unlisted Pages
Filename | Output Path | |
---|---|---|
entrypoints/{name}.html | /{name}.html | |
entrypoints/{name}/index.html | /{name}.html |
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<!-- Set include/exclude if the page should be removed from some builds -->
<meta name="manifest.include" content="['chrome', ...]" />
<meta name="manifest.exclude" content="['chrome', ...]" />
</head>
<body>
<!-- ... -->
</body>
</html>
At runtime, unlisted pages are accessible at /{name}.html
:
const url = browser.runtime.getURL('/{name}.html');
console.log(url); // "chrome-extension://{id}/{name}.html"
window.open(url); // Open the page in a new tab
Unlisted Scripts
Filename | Output Path | |
---|---|---|
entrypoints/{name}.[jt]sx? | /{name}.js | |
entrypoints/{name}/index.[jt]sx? | /{name}.js |
export default defineUnlistedScript(() => {
// Executed when script is loaded
});
export default defineUnlistedScript({
// Set include/exclude if the script should be removed from some builds
include: undefined | string[],
exclude: undefined | string[],
main() {
// Executed when script is loaded
},
});
At runtime, unlisted scripts are accessible from /{name}.js
:
const url = browser.runtime.getURL('/{name}.js');
console.log(url); // "chrome-extension://{id}/{name}.js"
You are responsible for loading/running these scripts where needed. If necessary, don't forget to add the script and/or any related assets to web_accessible_resources
.
When defining an unlisted script, keep in mind that WXT will import this file in a NodeJS environment during the build process. That means you cannot place any runtime code outside the main
function.
document.querySelectorAll('a').forEach((anchor) => {
// ...
});
export default defineUnlistedScript(() => {
document.querySelectorAll('a').forEach((anchor) => {
// ...
});
});
Refer to the Entrypoint Loaders documentation for more details.