wxt-module-layers
| Install | |
|---|---|
npm install wxt-module-layers |
|
| Latest Version: | 1.3.0 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Jul 14, 2026 |
| Links: | GitHub · npm |
WXT Layers
Nuxt-like layers functionality for WXT browser extensions

Abstract
WXT Layers brings Nuxt's layers architecture to browser extensions, enabling you to organise your extension into isolated, reusable slices:
src/ # core logic
composables/
entrypoints/
public/
utils/
layers/
some-feature/ # related files
entrypoints/
public/
some-service/ # related files
composables/
index.ts
Layers are self-contained folders that mirror WXT's project structure. Each layer can have its own entry points, auto-imports, public files, and manifest modifications, with some additional layer-specific ✨ sprinkled on top.
Main use cases:
- Large projects: a flexible level of abstraction once your project gets large
- Cleaner organisation: use core WXT structure for shared code, layers for specific features
- Feature isolation: keep related code together (e.g.,
layers/analytics/,layers/auth/)
Jump to the usage section for full details.
Demo

Check the demo folder for a working Hacker News enhancement extension — HN Enhanced — that shows WXT Layers in action with two isolated feature layers:
- Theme — replaces the favicon, highlights high-scoring stories, opens links in new tabs
- Tracker — marks read stories with a checkmark and orange highlight, logs your reading history to a sidepanel
It's a practical reference for layer discovery, per-layer background scripts, auto-imports, barrel file exports, public file copying, CSS assets, and manifest merging — all in one runnable project.
Contents
Quickly jump to:
- Setup
Get started quickly with install, config and files - Usage
Explore how the module works - Patterns
Best practices for setting up larger projects - Options
Detailed configuration information - Debugging
What to do when things don't work
Setup
Installation
Install via your preferred package manager:
npm install wxt-module-layers
Configuration
Configure wxt.config.ts:
import { defineConfig } from 'wxt'
export default defineConfig({
// enable the module
modules: ['wxt-module-layers'],
// options
layers: {
logLevel: 'debug', // set this to see what gets discovered and registered
}
})
Quick start
WXT Layers has sensible defaults to get started quickly. It looks for sub-folders under <root>/layers/ and expects each folder to mirror the structure of a regular WXT extension.
Let's add some files to get started:
layers/ # default source (note: you can configure multiple sources)
example/ # example layer
entrypoints/ # standard entrypoints folder
options.html # global options page
Right click your extension's icon, click "Options", and view the options page – loaded from a layer 😎
[!TIP] Your extension will rebuild as you add or update new files or configuration options during development. If you're feeling adventurous, try moving your popup entrypoint and related code and see what happens!
You can stop here if you're happy with the defaults, otherwise, read on to deep dive usage, patterns and options.
Usage
The main areas of interest in a Layers project are:
- Folder structure
A mirror of WXT's standard structure - Entrypoints
Automatic, manual, and background scripts - Imports and exports
Aliases, auto-imports, and index files - Public files and assets
Layer-specific public files and assets - Options (separate section)
Module, source and layer-level configuration
Folder Structure
Layers mirror the folder structure of a regular WXT extension:
src/
composables/ # Global composables
entrypoints/ # Global entrypoints
...
layers/ # Layers folder
analytics/ # Layer name Aliased to #<name>
entrypoints/ # Auto-discovered By folder or manual config
composables/ # Optional auto-import Off by default
public/ # Auto-copied To .output/<name>/*
...
layer.config.ts # Optional layer config
index.ts # Optional layer exports
All defaults are configurable (see options).
Entrypoints
There are several ways to configure and utilise entry points in layers:
- Entrypoints folders
Automatic discovery - Custom entrypoint locations
Manual configuration - Per-layer background scripts
Automatic or manual background script integration
Entrypoints Folders
File-based entrypoints are discovered in exactly the same way as a regular WXT project:
layers/my-layer/
entrypoints/
background.ts # background script
content.ts # content script
popup/ # popup
index.html
popup.ts
sidepanel.html # sidepanel
... # etc
Manual Entrypoint locations
The module also supports custom entry points:
layers/some-feature/
background/
services/
index.ts # non-standard location
panel/
services/
index.html # non-standard location
To do this, configure using an entrypoints object in your layer config:
// layers/some-feature/layer.config.ts
export default defineLayer({
entrypoints: {
background: 'background/index.ts', // output to background.js
sidepanel: 'panel/index.html', // output to sidepanel.html
}
})
[!TIP]
To configure multiple layers at once, use source options
Per-layer background scripts
Every layer can have its own background script; they are compiled at build time and either:
- automatically added to a virtual background endpoint if no existing
srcbackground endpoint - manually imported and run in your existing
srcbackground endpoint
Let's say you have two layers which need to add their own listeners, alarms, etc:
layers/
some-feature/entrypoints/background.ts
another-feature/entrypoints/background.ts
In each layer, define a background entrypoint as you normally would:
// layers/some-feature/entrypoints/background.ts
export default function () {
// set up listeners, alarms, etc.
}
[!Tip]
Create an
asyncfunction if you want layers to complete their work in sequence before the next one runs
If you have an existing src background entrypoint, manually import the layer bacgrounds initializer and run it:
// src/entrypoints/background.ts
import { main } from 'wxt-module-layers:background'
export default defineBackground(() => {
console.log('Main background ready')
main()
})
If you don't have, or don't need, an existing src background entrypoint, the module will automatically create and add a new background entrypoint that runs all layer backgrounds in order. No additional coding required!
If you need background scripts to run in a specific order, set the order property in layer config:
// layers/some-feature/layer.config.ts
export default defineLayer({
order: 0, // Lower values run earlier (default is 50)
})
[!Note]
During development, each background layer's
console.log()s will:
- output within a layer-named
console.group()entry- add execution timings
Imports and Exports
There are three main ways of orchestrating dependencies in layers:
- Layer aliases
Global layer aliases that work across sources - Auto-imports
Per-sources or per-layer auto-imports - Index files
Barrel files to expose layer dependencies
Layer Aliases
By default, all sources and layers are aliased using the layerAlias default template #{name}:
layers/ --> #layers
foo-feature/ --> #foo-feature
bar-feature/ --> #bar-feature
This allows for clearer imports between layers:
// src/entrypoints/background.ts
import { foo } from '#foo-feature' // imports from layers/foo-feature/index.ts
To reconfigure, see module, source and layer options.
Auto-imports
By default, auto-import is off for layers; the rationale being:
- a project's global concerns will generally be contained in
src/*folders - in larger projects, it's more manageable control layers' access explicitly
However, feel free to re-configure auto-imports per module, source or layer.
[!IMPORTANT] Restart your TypeScript server if your IDE doesn't pick up imports:
- VS Code:
Cmd/Ctrl + Shift + P→ "TypeScript: Restart TS Server"- WebStorm:
Shift + Shift→ "Restart TypeScript Service"Your IDE should update; if it doesn't, you might need to give it a minute!
Index Files
You can use index or "barrel" files to expose a layer's dependencies:
// layers/some-feature/index.ts
export * from './components'
export * from './utils'
...
Other layers then import explicitly:
import { foo, bar } from '#some-feature'
Public Files and Assets
Public files
Public files (i.e. those accessed via URL at runtime) from each layer will be copied to the main project's public folder:
layers/some-feature/
public/
icons/my-icon.png # some-feature/icons/my-icon.png
By default, public paths are prefixed with the layer name; reconfigure this at the module, source or layer level:
{
publicPrefix: '', // no prefix; ensure layer public files are unique!
publicPrefix: '{name}/', // default: prefix with layer name
}
By default, public files are accessible from <all_urls>; reconfigure this at the module, source or layer level:
{
publicAccess: ['*://wxt.dev'], // limit access to one or more domains
publicAccess: false, // prevent access to files
}
Asset files
Asset files (i.e. those bundled by rollup at build time) should be imported into source code:
// layers/some-feature/entrypoints/popup.ts
import '../assets/styles.scss' // styles will be compiled in the final bundle
import icon from '../assets/icon.svg?raw' // assets should be referenced directly in code
Patterns
This section contains best-practice information regarding:
Sources
Set up multiple sources for different purposes:
export default defineConfig({
srcDir: 'src', // core extension logic
layers: {
sources: [
'layers/*', // feature-specific layers
'packages/*', // reusable packages
]
}
})
Use source options to configure layers en-masse:
export default defineConfig({
...
layers: {
sources: [
{
source: 'features/*',
entrypoints: { background: 'background.ts' } // load custom background scripts
},
{
source: 'packages/*',
autoImports: ['composables'], // auto-import all composables
},
]
}
})
Core Extension Logic
Use WXT's base structure for shared functionality:
src/
entrypoints/ # Main extension entrypoints
background.ts
composables/ # Shared composables
useStorage.ts
useSettings.ts
utils/ # Shared utilities
logger.ts
api.ts
public/ # Public assets
icon.png
Feature Layers
Isolate specific features in their own layers:
layers/
analytics/
entrypoints/
content.ts # Track page views
services/
tracker.ts
public/
icons/
auth/
entrypoints/
background.ts # Handle auth state
popup.html # Login UI
composables/
useAuth.ts
index.ts # optional barrel file export
Import between layers using aliases (if auto-imports are off):
// src/entrypoints/content.ts
import { useAuth } from '#auth'
Package Layers
Create reusable layers for common functionality across projects:
packages/
error-reporting/ # Reusable Sentry integration
feature-flags/ # Reusable feature flag system
telemetry/ # Reusable analytics
Note that layers can be installed from any relative or absolute path:
{
sources: [
// multiple layers
'src/layers/*',
'packages/*',
// single layers
'~/Projects/.../some-package',
'/Volumes/Projects/.../some-package',
'node_modules/some-package/',
]
}
[!NOTE] WXT Layers does not currently support installing directly from Github, but you can install and share layers from NPM by referencing the
node_modules/<package_name>folder directly.
Import Strategies
Different ways to import code in your extension.
Source Imports (~/)
Core utilities, types, and shared code in your base WXT structure
Import from your main src/ directory:
import { logger } from '~/utils/logger'
import type { User } from '~/types'
Auto-imports (Global)
Use-it-from-anywhere, low-friction, less typing...
If you set autoImports in module, source, or layer options...
// layers/some-feature/layer.config.ts
export default defineLayer({
autoImports: ['composables'],
})
...exports in that folder...
// layers/some-feature/composables/someThing.ts
export function someThing () { /* ... */ }
...will be globally available:
// Anywhere:
const track = someThing() // Just works
Layer Global (#some-layer)
Cross-layer dependencies, services, and types
Optionally, create a barrel file in layer root folder:
// layers/analytics/index.ts
export * from './services/tracker'
export * from './composables/useTracking'
Then, import from other layers:
import { AuthService } from '#auth'
import { trackEvent } from '#analytics'
Layer Local (../)
Internal layer code that shouldn't be exposed outside
Import from the same layer:
// Within layers/analytics/
import { TrackerService } from '../services/tracker'
import type { Event } from '../types'!
Extensibility
Other plugins or your own code can be informed when layers are resolved, by hooking into the 'layers:resolved' event:
wxt.hook('layers:resolved' as any, async (layerDirs: string[]) => {
// do something with layer dirs
})
The WXT Pages module does just this to add file based routes from individual layers.
Options
Options are hierarchical, configurable at module > source > layer levels:
| Name | Module | Source | Layer | Description |
|---|---|---|---|---|
logLevel |
✅ | Logger output level | ||
sources |
✅ | Paths/globs to layer sources | ||
source |
✅ | Path/glob to layer sources | ||
layerAlias |
✅ | ✅ | ✅ | Layer alias configuration |
autoImports |
✅ | ✅ | ✅ | Auto-import folder paths |
entrypoints |
✅ | ✅ | ✅ | Manual entry point configuration |
publicPrefix |
✅ | ✅ | ✅ | Copied public files location |
publicAccess |
✅ | ✅ | ✅ | Copied public files accessibility |
order |
✅ | Background script execution order | ||
manifest |
✅ | Raw manifest modification |
Module Options
Module options are configured in wxt.config.ts under the layers key:
// Ktchen sink example of all configuration options (not representative of a typical project)
export default defineConfig({
modules: ['wxt-module-layers'],
layers: {
// Where to find layers (default: 'layers/*')
sources: [
'layers/*', // All folders under /layers/
'src/packages/*', // All folders under /src/packages/
'vendor/analytics', // Single specific layer
{
source: 'features/*', // Options per source (see Source Options below)
sourceAlias: ...,
layerAlias: ...,
entrypoints: ...,
autoImports: ...,
publicPrefix: ...,
publicAccess: ...,
}
],
// Default source alias template (default: '#{name}')
sourceAlias: '@{name}', // i.e. @features, @packages, etc.
// Default layer alias template (default: '#{name}')
layerAlias: '@{name}', // i.e. @analytics, @auth, etc.
// Default auto-import folders (default: [], no auto-imports)
autoImports: [
'composables',
'utils',
...
],
// Default manual entrypoint files (default: null, scans entrypoint folders)
entrypoints: {
background: 'bg.ts', // add custom background endpoint location
...
},
// Default public file prefix (default: '{name}', copy into subfolder)
publicPrefix: '/', // Copied to '/' rather than '/auth/'
// Default public file accessibility (default: false, nothing added to `web_accessible_resources`)
publicAccess: '*://wxt.dev', // make all files in `public` folders accessible on wxt.dev
// Logging level (default: 'info')
logLevel: 'debug', // Exposes useful debugging information
}
})
[!NOTE] In all options, configuration such as
#{name}will be replaced with the relevant source or layer name
Source Options
You can mass-assign layer options to sources layers by supplying objects rather than paths:
export default defineConfig({
...
layers: {
sources: [
// configure path only
'packages/*',
// configure options for all layers in source
{
source: 'packages/*', // all folders under features
sourceAlias: '@packages', // add an alias to the source folder
layerAlias: '', // don't add layer aliases
autoImports: ['utils'], // auto-import everything from utils folder
entrypoints: {
background: 'bg.ts' // add custom background endpoint location
},
publicPrefix: '/', // place all public files in root
publicAccess: '*://wxt.dev', // make public files only accessible from wxt.dev
},
],
},
})
Note that source options fall back to module options if not set.
Layer Options
Most layers work just-fine with defaults. Configure when you need to:
- control background script execution order
- use non-standard entry point locations
- modify the extension manifest
- modify aliases (not-recommended at layer level for consistency)
- modify auto-imports
- modify
publicfiles location - modify
publicfiles access
// layers/analytics/layer.config.ts
import { defineLayer } from 'wxt-module-layers'
export default defineLayer({
// Control background script order (default: 50, lower = earlier)
order: 0,
// Manually specify entry points (bypasses auto-discovery)
entrypoints: {
'background': 'background/index.ts', // --> background.ts
'linkedin.content': 'content/linkedin.ts', // --> content-scripts/linkedin.ts
'twitter.content': 'content/twitter.ts', // --> content-scripts/twitter.ts
},
// Modify extension manifest
manifest: (wxt, manifest) => {
manifest.permissions?.push('storage', 'cookies')
manifest.host_permissions?.push('*://*.example.com/*')
},
// Override module defaults (rarely needed)
layerAlias: '@tracking',
// Auto-import specific folders
autoImports: ['composables', 'services'],
// Customise public files location
publicPrefix: 'tracking',
// Customise public files access
publicAccess: false,
})
Note that layer options fall back to source and module options if not set.
Entrypoints Options
Manual entry point options should be configured using a key => path format.
[!NOTE] The
keystring mirrors WXT's filename naming conventions:
// layers/some-feature/layer.config.ts
export default defineLayer({
entrypoints: {
// virtual-background
'background': '<path>.ts',
// single entrypoint, per context
// popup, options, bookmarks, history, new tab, devtools
'<context>': '<path>.html',
// named entrypoints, per context (names must be unique!)
// sidepanel, sandbox, content script, unlisted page, script or css
'<name>.<context>': '<path>.<ext>',
}
})
[!Important] Custom entry point names MUST be unique! See the debugging section for troubleshooting output errors.
The following example demonstrates naming for separate content scripts:
export default defineLayer({
entrypoints: {
'linkedin.content': 'content/linkedin.ts', // content-scripts/linkedin.js
'twitter.content': 'content/twitter.ts', // content-scripts/twitter.js
'github.content': 'content/github.ts', // content-scripts/github.js
}
})
Debugging
Enable Logging
export default defineConfig({
layers: {
logLevel: 'debug',
}
})
Terminal output shows what's discovered:
[layers] [source]: layers/*
[layers] - alias: #layers
[layers] [layer]: layers/complex
[layers] - alias: #complex
[layers] - entrypoint: entrypoints/background.ts (layer-background)
[layers] - auto-imports: composables, services
...
Layer background scripts will:
- wrap their layers'
console.log()s in named groups - output execution times
Common Issues
Aliases not resolving:
Auto-imports not working:
- Check
autoImportsare added, i.e.['composables', 'utils', ...] - Verify WXT's own imports aren't disabled
Entrypoints not found:
- Use module debug logging to see what's scanned (
logLevel: 'debug') - Check file naming matches WXT conventions
- Check no duplicate entrypoint names
- Consider manual
entrypointsconfig
Entrypoint naming
Use this table to understand how WXT converts entry point identifiers to filenames:
| Usage | Key | Source | Target |
|---|---|---|---|
| Virtual | |||
background |
*.[jt]s |
background.js |
|
| Single | |||
bookmarks |
*.html |
bookmarks.html |
|
devtools |
*.html |
devtools.html |
|
history |
*.html |
history.html |
|
newtab |
*.html |
newtab.html |
|
options |
*.html |
options.html |
|
popup |
*.html |
popup.html |
|
sandbox |
*.html |
sandbox.html |
|
sidepanel |
*.html |
sidepanel.html |
|
content |
*.[jt]sx? |
content-scripts/content.js |
|
content |
*.css,scss,... |
content-scripts/content.css |
|
| Multiple | |||
{name}.sidepanel |
*.html |
{name}.html |
|
{name}.sandbox |
*.html |
{name}.html |
|
| Content | |||
{name}.content |
*.[jt]sx? |
content-scripts/{name}.js |
|
{name}.content |
*.css,scss,... |
content-scripts/{name}.css |
|
| Unlisted | |||
{name} |
*.html |
{name}.html |
|
{name} |
*.[jt]sx? |
{name}.js |
|
{name} |
*.css,scss,... |
{name}.css |
WXT will error if duplicate entrypoint names are found:
ERROR Multiple entrypoints with the same name detected, only one entrypoint for each name is allowed.
- foo
- entrypoints/foo.content.ts
- entrypoints/foo.html
Related Packages
Use Vue CLI generate chrome extension template | generate chrome extension with...
Version History
| Version | Released | License |
|---|---|---|
| 1.3.0 | MIT |
Pre-releases (1)
| Version | Released | License |
|---|---|---|
| 0.0.1-pakagetransfer | ISC |