Difficulty: Advanced
What is module augmentation in TypeScript? How do you extend existing library types without modifying their source?
Module augmentation lets you add new declarations to existing modules without touching the original source code. It works because TypeScript supports declaration merging: when two declarations of the same name exist, they are merged.
Common use cases: 1. Adding properties to Express Request/Response 2. Extending third-party library types with plugin methods 3. Adding global utility types or variables 4. Patching incomplete or incorrect library types
Syntax: Use 'declare module "module-name"' to augment an external module. The augmentation must be in a module file (has import/export).
// express-augment.d.ts
import { User } from './models/user';
// Augment Express Request to include user from auth middleware
declare module 'express-serve-static-core' {
interface Request {
user?: User;
requestId: string;
startTime: number;
}
}
// Now in your middleware and routes:
import { Request, Response, NextFunction } from 'express';
function authMiddleware(req: Request, res: Response, next: NextFunction) {
const token = req.headers.authorization;
req.user = verifyToken(token); // OK - user exists on Request
req.requestId = crypto.randomUUID(); // OK
req.startTime = Date.now(); // OK
next();
}
function getProfile(req: Request, res: Response) {
if (!req.user) return res.status(401).json({ error: 'Not authenticated' });
res.json(req.user); // Fully typed!
}
Augmenting Express is one of the most common real-world uses. By extending the Request interface, your auth middleware and route handlers share type-safe user data.
// Augmenting a date library with custom methods
declare module 'dayjs' {
interface Dayjs {
toBusinessDay(): Dayjs;
isBusinessDay(): boolean;
}
}
// Augmenting a state management library
declare module 'zustand' {
interface StoreApi<T> {
getServerState(): T;
}
}
// Extending a UI library's theme
declare module '@mui/material/styles' {
interface Theme {
brand: {
primary: string;
secondary: string;
};
}
interface ThemeOptions {
brand?: {
primary?: string;
secondary?: string;
};
}
}
// Interface merging within your own code
interface EventMap {
click: MouseEvent;
keypress: KeyboardEvent;
}
// Later in another file - extends EventMap
interface EventMap {
scroll: Event;
resize: UIEvent;
}
// EventMap now has all four members
Module augmentation and interface merging allow you to add types to libraries without forking them. This is critical for typed middleware, plugins, and theme systems.
// Adding to a namespace
// Original library declares:
// declare namespace Express {
// interface Request { ... }
// }
// You augment it:
declare namespace Express {
interface Request {
session: {
userId: string;
role: 'admin' | 'user';
};
}
}
// Global augmentation (must be in a module file)
export {}; // Makes this a module
declare global {
function sleep(ms: number): Promise<void>;
interface Array<T> {
toShuffled(): T[];
unique(): T[];
}
}
// Implementation (runtime)
Array.prototype.unique = function<T>(this: T[]): T[] {
return [...new Set(this)];
};
// Now globally available with types
const nums = [1, 2, 2, 3, 3];
nums.unique(); // [1, 2, 3] - typed as number[]
Global augmentation uses 'declare global' inside a module file. It can extend built-in types like Array and add global functions. Use sparingly as it affects the entire project.
Module Augmentation, Declaration Merging, Extending Libraries, Global Augmentation, Ambient Modules