Skip to content
TypeScript ts modules 3 min read

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 you import "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

TargetAugmentable?How
interface in a moduleYesdeclare module "x" { interface Y { ... } }
namespace in a moduleYesmerge into the namespace
type aliasNoaliases never merge
Global interface (e.g. Window)Yesdeclare global { interface Window { ... } }
Adding a new unrelated exportNoaugmentation 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 module specifier exactly to how consumers import the module.
  • Augment interface/namespace declarations only — type aliases cannot be merged.
  • Add import "..." or export {} so the augmenting file is treated as a module.
  • Use declare global to extend global types like Window and NodeJS.ProcessEnv.
  • Keep augmentations in dedicated .d.ts files and confirm they are in tsconfig include.
  • Prefer augmentation over editing files in node_modules, which never survives reinstalls.
  • Document why each augmentation exists so future maintainers understand the extension.
Last updated June 29, 2026
Was this helpful?