Skip to content

Reusable Modules

Overview

WXT provides a "module" API that lets you modify the build process. This API lets you add entrypoints, inject runtime code, add vite plugins, and more!

What's more, these modules can be shared on NPM and re-used between projects!

Adding a Module

There are two ways to add a module to your project:

  1. Local file: Any file present in the modules/ directory will be treated as a module and loaded at build-time by WXT. You can use modules/*.ts or modules/*/index.ts, similar to entrypoints.

    ts
    // modules/example.ts
    import { defineWxtModule } from 'wxt/modules';
    
    export default defineWxtModule((wxt) => {
      // ...
    });
  2. NPM package: By convention, any package on NPM that starts with @wxt-dev/module-* or wxt-module-* can be installed and included in your project:

    ts
    // wxt.config.ts
    export default defineConfig({
      // Add the module to your project
      modules: ['wxt-module-example'],
    });

Writing Modules

Modules contain a setup function that is executed at the beginning of the build process.

ts
import { defineWxtModule } from 'wxt/modules';

export default defineWxtModule((wxt) => {
  // ...
});
ts
import { defineWxtModule } from 'wxt/modules';

export default defineWxtModule({
  // Add metadata...
  setup(wxt) {
    // ...
  },
});

Module Options

You can define custom options for your module by setting the configKey:

ts
// modules/analytics.ts
import { defineWxtModule } from 'wxt/modules';

export default defineWxtModule<AnalyticsModuleOptions>({
  configKey: 'analytics',
  setup(wxt, options) {
    console.log(options); // { clientId: "..." }
  },
});

// Define the option types
export interface AnalyticsModuleOptions {
  clientId: string;
}

// Use "module augmentation" to add types for the new key
declare module 'wxt' {
  export interface InlineConfig {
    analytics: AnalyticsModuleOptions;
  }
}

Now, when the user provides options to the analytics key in their wxt.config.ts, those options are passed into the setup function as the second argument.

ts
export default defineConfig({
  analytics: { clientId: '...' },
});

Actually Doing Something

The first argument of the setup function, wxt, provides full access to the current build's context. You can access the resolved configuration via wxt.config, or setup hooks to manipulate the build at different steps of the build process.

Here's an example that shows you how to update the outDir based on the build mode. It does a good job showing how to access config, set up a hook, and do something when the hook is called.

ts
export default defineWxtModule((wxt) => {
  if (wxt.config.mode === 'development') {
    // Use the "ready" hook to update wxt.config
    wxt.hooks.hook('ready', (wxt) => {
      wxt.config.outDir = wxt.config.outDir.replace('.output', '.output/dev');
    });
  }
});

Async Modules

Both the setup function and hook callbacks can be async. Don't forget to add await!

It's important to understand the basics of how hooks work. Make sure to read the API reference for the full list of hooks and what they should be used for. They are the key to modifying your extension.

Module Utils

Additionally, WXT provides several helper functions that setup hooks behind the scenes to streamline common operations.

For example, if you want to include an entrypoint from inside a module, you can use the addEntrypoint util:

ts
// modules/changelog.ts
import { defineWxtModule, addEntrypoint } from 'wxt/modules';
import { resolve } from 'node:path';

export default defineWxtModule({
  name: 'changelog',
  setup(wxt) {
    addEntrypoint(wxt, {
      type: 'unlisted-page',
      name: 'changelog',
      // Point to the "modules/changelog.html" file
      inputPath: resolve(__dirname, 'changelog.html'),
      outputDir: wxt.config.outputDir,
      options: {},
    });
  },
});

Refer to the API reference for the full list of the utilities.

Plugins

Whereas modules are executed at build-time, plugins are executed at runtime. As of now, the only way to add a plugin is with the addWxtPlugin helper inside a module.

Here's a minimal example to execute something at runtime.

ts
import { defineWxtModule, addWxtPlugin } from 'wxt/modules';
import { resolve } from 'node:path';

export default defineWxtModule((wxt) => {
  addWxtPlugin(wxt, resolve(__dirname, 'plugin.ts'));
});
ts
import { defineWxtPlugin } from 'wxt/sandbox';

export default defineWxtPlugin(() => {
  console.log('Executing plugin!');
});

Async Plugins

Unlike modules, plugins cannot be async!! If you need to do some async work and expose that result to the rest of the extension, wrap it in an object and handle awaiting the promise internally.

Publishing to NPM

🚧 Under construction

These docs will be coming soon!