Module Augmentation
Module augmentation lets you add to types that already exist in another module — without forking it or editing node_modules. It builds on declaration merging: a declare module block targeting an existing module merges its members into that module’s exports. This is how you add properties to a third-party interface, register plugin methods on a library, type a custom Express request field, or extend the global scope. This page shows how to augment modules and globals safely, and the rules that make it work.
How augmentation works
To augment an existing module, write declare module "name" with the module’s exact import specifier, then add members. The block must live in a file that is itself a module (it has a top-level import/export), so TypeScript treats it as an augmentation rather than a new ambient module.
// express-augment.d.ts
import "express"; // ensures this file is a module + loads the target
declare module "express" {
interface Request {
// add a property the auth middleware attaches
user?: { id: string; role: "admin" | "user" };
}
}
Now req.user is typed everywhere express is imported. The new user property merges into Express’s existing Request interface rather than replacing it.
The
declare module "..."specifier must match how the module is imported. If youimport "express", augment"express"— not a relative path or a subpath, unless that is exactly what consumers import.
Augmenting library types
Plugin ecosystems rely on augmentation so add-ons can register their own options on a core type. For example, adding a custom theme field to a config interface:
import "vite";
declare module "vite" {
interface UserConfig {
myPlugin?: {
enabled: boolean;
level: 1 | 2 | 3;
};
}
}
Only interface (and namespace) declarations merge. You cannot augment a type alias, and you cannot add brand-new top-level exports that did not exist — augmentation extends existing declarations, it does not invent unrelated ones.
Extending the global scope
To add to global types from inside a module file, wrap the declarations in declare global. This is the standard way to type globalThis, process.env, or window additions while keeping your file a module.
// env.d.ts
export {}; // make this file a module
declare global {
interface Window {
analytics: { track(event: string): void };
}
namespace NodeJS {
interface ProcessEnv {
DATABASE_URL: string;
NODE_ENV: "development" | "production" | "test";
}
}
}
After this, window.analytics.track(...) and process.env.DATABASE_URL (typed as string) are available globally. The export {} is what turns the file into a module so declare global is valid.
What can and cannot merge
| Target | Augmentable? | How |
|---|---|---|
interface in a module | Yes | declare module "x" { interface Y { ... } } |
namespace in a module | Yes | merge into the namespace |
type alias | No | aliases never merge |
Global interface (e.g. Window) | Yes | declare global { interface Window { ... } } |
| Adding a new unrelated export | No | augmentation only extends existing declarations |
A practical pattern: typed env
A common real-world use is making process.env strongly typed instead of string | undefined for known keys. Keep the augmentation in a dedicated .d.ts included by your tsconfig.
// types/env.d.ts
export {};
declare global {
namespace NodeJS {
interface ProcessEnv {
PORT: string;
JWT_SECRET: string;
}
}
}
// usage anywhere in the project
const port = Number(process.env.PORT); // PORT is string, not string | undefined
const secret = process.env.JWT_SECRET; // typed
Ensure the file is picked up by include in tsconfig (or sits inside rootDir) so the augmentation applies project-wide.
Best Practices
- Match the
declare modulespecifier exactly to how consumers import the module. - Augment
interface/namespacedeclarations only —typealiases cannot be merged. - Add
import "..."orexport {}so the augmenting file is treated as a module. - Use
declare globalto extend global types likeWindowandNodeJS.ProcessEnv. - Keep augmentations in dedicated
.d.tsfiles and confirm they are in tsconfiginclude. - Prefer augmentation over editing files in
node_modules, which never survives reinstalls. - Document why each augmentation exists so future maintainers understand the extension.