WXT Modules
WXT provides a "module system" that let's you run code at different steps in the build process to modify it.
Adding a Module
There are two ways to add a module to your project:
- NPM: install an NPM package, like
@wxt-dev/auto-icons
and add it to your config:ts// wxt.config.ts export default defineConfig({ modules: ['@wxt-dev/auto-icons'], });
Searching for "wxt module" on NPM is a good way to find published WXT modules.
- Local: add a file to your project's
modules/
directory:<srcDir>/ modules/ my-module.ts
To learn more about writing your own modules, read the Writing Modules docs.
Module Options
WXT modules may require or allow setting custom options to change their behavior. There are two types of options:
- Build-time: Any config used during the build process, like feature flags
- Runtime: Any config accessed at runtime, like callback functions
Build-time options are placed in your wxt.config.ts
, while runtime options is placed in the app.config.ts
file. Refer to each module's documentation about what options are required and where they should be placed.
If you use TypeScript, modules augment WXT's types so you will get type errors if options are missing or incorrect.
Execution Order
Modules are loaded in the same order as hooks are executed. Refer to the Hooks documentation for more details.
Writing Modules
Here's what a basic WXT module looks like:
import { defineWxtModule } from 'wxt/modules';
export default defineWxtModule({
setup(wxt) {
// Your module code here...
},
});
Each module's setup function is executed after the wxt.config.ts
file is loaded. The wxt
object provides everything you need to write a module:
- Use
wxt.hook(...)
to hook into the build's lifecycle and make changes - Use
wxt.config
to get the resolved config from the project'swxt.config.ts
file - Use
wxt.logger
to log messages to the console - and more!
Refer to the API reference for a complete list of properties and functions available.
Also to make sure and read about all the hooks that are available - they are essential to writing modules.
Recipes
Modules are complex and require a deeper understanding of WXT's code and how it works. The best way to learn is by example.
Update resolved config
import { defineWxtModule } from 'wxt/modules';
export default defineWxtModule({
setup(wxt) {
wxt.hook('ready', () => {
wxt.config.outDir = 'dist';
});
},
});
Add built-time config
import { defineWxtModule } from 'wxt/modules';
import 'wxt';
export interface MyModuleOptions {
// Add your build-time options here...
}
declare module 'wxt' {
export interface InlineConfig {
// Add types for the "myModule" key in wxt.config.ts
myModule: MyModuleOptions;
}
}
export default defineWxtModule<AnalyticModuleOptions>({
configKey: 'myModule',
// Build time config is available via the second argument of setup
setup(wxt, options) {
console.log(options);
},
});
Add runtime config
import { defineWxtModule } from 'wxt/modules';
import 'wxt/sandbox';
export interface MyModuleRuntimeOptions {
// Add your runtime options here...
}
declare module 'wxt/sandbox' {
export interface WxtAppConfig {
myModule: MyModuleOptions;
}
}
Runtime options are returned when calling
const config = useAppConfig();
console.log(config.myModule);
This is very useful when generating runtime code.
Generate output file
import { defineWxtModule } from 'wxt/modules';
export default defineWxtModule({
setup(wxt) {
// Relative to the output directory
const generatedFilePath = 'some-file.txt';
wxt.hook('build:publicAssets', (_, assets) => {
assets.push({
relativeDest: generatedFilePath,
contents: 'some generated text',
});
});
wxt.hook('build:manifestGenerated', (_, manifest) => {
manifest.web_accessible_resources ??= [];
manifest.web_accessible_resources.push({
matches: ['*://*'],
resources: [generatedFilePath],
});
});
},
});
This file could then be loaded at runtime:
const res = await fetch(browser.runtime.getURL('/some-text.txt'));
Generate runtime module
Create a file in .wxt
, add an alias to import it, and add auto-imports for exported variables.
import { defineWxtModule } from 'wxt/modules';
import { resolve } from 'node:path';
export default defineWxtModule({
imports: [
// Add auto-imports
{ from: '#analytics', name: 'analytics' },
{ from: '#analytics', name: 'reportEvent' },
{ from: '#analytics', name: 'reportPageView' },
],
setup(wxt) {
const analyticsModulePath = resolve(
wxt.config.wxtDir,
'analytics/index.ts',
);
const analyticsModuleCode = `
import { createAnalytics } from 'some-module';
export const analytics = createAnalytics(useAppConfig().analytics);
export const { reportEvent, reportPageView } = analytics;
`;
addAlias(wxt, '#analytics', analyticsModulePath);
wxt.hook('prepare:types', async (_, entries) => {
entries.push({
path: analyticsModulePath,
text: analyticsModuleCode,
});
});
},
});
Generate declaration file
import { defineWxtModule } from 'wxt/modules';
import { resolve } from 'node:path';
export default defineWxtModule({
setup(wxt) {
const typesPath = resolve(wxt.config.wxtDir, 'my-module/types.d.ts');
const typesCode = `
// Declare global types, perform type augmentation
`;
wxt.hook('prepare:types', async (_, entries) => {
entries.push({
path: 'my-module/types.d.ts',
text: `
// Declare global types, perform type augmentation, etc
`,
// IMPORTANT - without this line your declaration file will not be a part of the TS project:
tsReference: true,
});
});
},
});
Example Modules
You should also look through the code of modules other people have written and published. Here's some examples: