Modules
Nitro modules are functions that run once when the Nitro instance is initialized (in development, build, and prerendering). They receive the nitro build context, which they can use to modify options, register build-time hooks, add virtual files, or register route handlers.
| Modules | Plugins | |
|---|---|---|
| Run | Once, during initialization and build | Once, on server startup |
| Receive | nitro (build context) | nitroApp (runtime app) |
| Typical use | Modify config, add handlers and virtual files, hook into build | Hook into request/response lifecycle |
| Bundled into output | No | Yes |
Using modules
Register modules with the modules config array:
import { defineConfig } from "nitro";
export default defineConfig({
modules: [
// Path to a local module (relative to the project root)
"./modules/my-module.ts",
// Name of an npm package
"my-nitro-module",
// Inline module object
{
name: "inline-module",
setup(nitro) {
nitro.logger.info("Inline module setup");
},
},
// Bare setup function
(nitro) => {
nitro.hooks.hook("compiled", () => {
// ...
});
},
],
});
Each entry can be one of the following forms:
| Form | Example | Description |
|---|---|---|
| Path or package string | "./modules/my-module.ts", "my-nitro-module" | Resolved from the project root and imported. The default export is used as the module. |
| Module object | { name?, setup } | An object with a setup(nitro) function and an optional name. |
| Bare function | (nitro) => { ... } | A shorthand for { setup }. |
| Wrapper object | { nitro: { name?, setup } } | An object with a nitro key. Useful for packages that integrate with multiple tools from a single export. |
Files in the modules/ directory are automatically registered as modules (like plugins/ for runtime plugins).
Modules referenced by path or package name are resolved and installed only once — duplicate entries pointing to the same file are skipped.
Authoring a module
A module is an object with a setup function receiving the Nitro instance. Use the NitroModule type from nitro/types for type safety:
import type { NitroModule } from "nitro/types";
export default {
name: "hello",
setup(nitro) {
// Add a virtual module usable anywhere in the server code as `#hello`
nitro.options.virtual["#hello"] = `export const hello = "world";`;
// Register a route handler pointing to the virtual module
nitro.options.handlers.push({
route: "/_hello",
handler: "#hello-handler",
});
nitro.options.virtual["#hello-handler"] = /* ts */ `
import { defineHandler } from "nitro";
import { hello } from "#hello";
export default defineHandler(() => ({ hello }));
`;
},
} satisfies NitroModule;
The setup function can be async. Useful properties on the nitro instance:
| Property | Description |
|---|---|
nitro.options | Resolved config (mutable): handlers, virtual, plugins, runtimeConfig, and every other option. |
nitro.hooks | Build-time hooks (hookable instance). |
nitro.logger | Tagged consola logger for build-time output. |
nitro.meta | Nitro version info (version, majorVersion). |
Build-time hooks
Modules can hook into the build lifecycle with nitro.hooks.hook():
import type { NitroModule } from "nitro/types";
export default {
name: "build-info",
setup(nitro) {
nitro.hooks.hook("compiled", () => {
nitro.logger.info(`Server built in ${nitro.options.output.dir}`);
});
},
} satisfies NitroModule;
The most useful hooks:
| Hook | Signature | When it runs |
|---|---|---|
types:extend | (types) => void | When generating TypeScript types. Extend generated route types and tsConfig. |
build:before | (nitro) => void | Before the build starts, before the bundler config is created. |
rollup:before | (nitro, config) => void | After the bundler (Rollup / Rolldown) config is resolved, right before bundling. Last chance to modify it. |
compiled | (nitro) => void | After the server bundle is written to the output directory. |
dev:start | () => void | In development, when the dev worker starts. |
dev:reload | (payload?) => void | In development, after each rebuild when the dev worker reloads. |
dev:error | (cause?) => void | In development, when a build error occurs. |
prerender:routes | (routes: Set<string>) => void | Before prerendering. Add or remove routes to prerender. |
prerender:config | (config) => void | When the prerenderer's Nitro config is created. |
prerender:generate | (route, nitro) => void | For each route, before it is generated. |
prerender:route | (route) => void | For each route, after it is generated. |
prerender:done | ({ prerenderedRoutes, failedRoutes }) => void | After prerendering finishes. |
close | () => void | When the Nitro instance is closed. |
All hooks can be async. See the hooks source for the full list and exact signatures, and the prerendering guide for the prerender:* hooks in context.
request, response, error) are not available here — they belong to plugins. A module can still add runtime behavior by pushing a plugin file to nitro.options.plugins.Best practices
- Keep modules idempotent. In development, options can be re-processed on config changes — check before pushing duplicate handlers, plugins, or virtual entries.
- Prefer hooks over patching. Use build hooks to react to lifecycle events instead of monkey-patching Nitro internals or overwriting functions.
- Respect user options. Read existing values from
nitro.optionsand extend them instead of replacing them. Let users opt out of your module's behavior. - Use
nitro.loggerfor build-time output instead ofconsole. - Mind the runtime bundle. Any handler or plugin your module adds is bundled for every deployment target — keep runtime code minimal and runtime-agnostic.
Distributing modules
To share a module as an npm package, export the module object as the default export and users can reference it by package name:
import type { NitroModule } from "nitro/types";
export default {
name: "my-nitro-module",
setup(nitro) {
// ...
},
} satisfies NitroModule;
import { defineConfig } from "nitro";
export default defineConfig({
modules: ["my-nitro-module"],
});
Add nitro as a peer dependency, and import types only from nitro/types so your package does not bundle Nitro itself.