8.3 Module Augmentation — Extending Existing Types
What is Module Augmentation?
Module Augmentation is a technique to modify or extend type definitions of existing modules without touching the library source code.
// Adding user info types to the existing express module
import 'express';
declare module 'express' {
interface Request {
user?: AuthUser; // Adding user property to existing Request
sessionId?: string;
}
}
Why Module Augmentation is Needed
The Problem
import express, { Request, Response } from 'express';
// Implementing JWT middleware
function authMiddleware(req: Request, res: Response, next: Function) {
const token = req.headers.authorization;
const user = verifyToken(token);
req.user = user; // ❌ Property 'user' does not exist on type 'Request'
next();
}
Solution: Module Augmentation
// src/types/express.d.ts
import { AuthUser } from '../models/user';
declare module 'express-serve-static-core' {
interface Request {
user?: AuthUser;
}
}
// No more type errors
function authMiddleware(req: Request, res: Response, next: Function) {
req.user = verifyToken(req.headers.authorization); // ✅
next();
}
Basic Syntax
External Module Augmentation
// Must have an import to be recognized as a module file
import type { SomeType } from 'some-library';
declare module 'some-library' {
// Extend existing interface
interface ExistingInterface {
newProperty: string;
}
// Add new function
function newFunction(arg: string): void;
}
Interface Merging (Declaration Merging)
TypeScript automatically merges multiple declarations of the same interface name.
// Library's original definition
interface Window {
location: Location;
history: History;
}
// Our additions
interface Window {
myPlugin: MyPlugin;
analytics: Analytics;
}
// TypeScript merges both declarations
// Window = { location, history, myPlugin, analytics }
Real-World Examples
Example 1: Express Request Extension
// src/types/express-ext.d.ts
import type { User } from '../models/user';
import type { Logger } from '../utils/logger';
declare module 'express-serve-static-core' {
interface Request {
user?: User;
logger: Logger;
startTime: number;
requestId: string;
}
}
// src/middleware/auth.ts
import { Request, Response, NextFunction } from 'express';
export function authMiddleware(req: Request, res: Response, next: NextFunction) {
try {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) throw new Error('No token');
req.user = verifyJwt(token); // ✅ Type safe
req.requestId = generateId(); // ✅
next();
} catch {
res.status(401).json({ error: 'Unauthorized' });
}
}
Example 2: Extending the Global Window Object
// src/types/global.d.ts
interface Window {
// Google Analytics
gtag: (command: string, id: string, params?: Record<string, unknown>) => void;
// App configuration
__APP_CONFIG__: {
apiUrl: string;
version: string;
featureFlags: Record<string, boolean>;
};
// Stripe
Stripe?: (key: string) => StripeInstance;
}
// Usage
window.gtag('event', 'page_view', { page_path: '/home' }); // ✅
const config = window.__APP_CONFIG__;
console.log(config.version); // ✅
Example 3: Extending Library Interfaces
// Fastify plugin type extension
import 'fastify';
declare module 'fastify' {
interface FastifyInstance {
db: DatabaseClient;
redis: RedisClient;
jwt: JwtHelper;
}
interface FastifyRequest {
user?: AuthUser;
session: SessionData;
}
}
// Fastify plugin implementation
import fp from 'fastify-plugin';
export default fp(async (fastify) => {
const db = await createDatabaseClient();
fastify.decorate('db', db); // ✅ fastify.db is type safe
});
// Usage in routes
fastify.get('/users', async (request, reply) => {
const users = await request.server.db.query('SELECT * FROM users'); // ✅
return users;
});
Example 4: process.env Type Definitions
// src/types/env.d.ts
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production' | 'test';
DATABASE_URL: string;
JWT_SECRET: string;
PORT?: string;
API_KEY?: string;
}
}
// Type-safe usage
const dbUrl: string = process.env.DATABASE_URL; // ✅ string (not undefined)
const port = parseInt(process.env.PORT ?? '3000', 10); // ✅
// ❌ Typos caught immediately
process.env.TYPO_KEY; // Property 'TYPO_KEY' does not exist
Global Type Declarations
Global declarations in script files (without import/export).
// src/types/global.d.ts (no import/export)
// Global type declarations
type ID = string | number;
type Nullable<T> = T | null;
type Optional<T> = T | undefined;
// Global interface
interface Pagination {
page: number;
limit: number;
total: number;
}
// Global function
declare function log(message: string, level?: 'info' | 'warn' | 'error'): void;
// Usable anywhere without import
const id: ID = '123'; // ✅
const page: Pagination = { page: 1, limit: 10, total: 100 }; // ✅
Note: Files with
importorexportare treated as modules. Usedeclare global {}for global declarations.
// src/types/augment.d.ts (global declaration even with imports)
import type { User } from './user';
// Global declarations go inside declare global
declare global {
interface Window {
currentUser: User;
}
type ID = string | number;
}
export {}; // Empty export to make it a module file
Module Augmentation Caveats
1. Verify the Correct Module Name
// For Express, the actual module name is different!
declare module 'express' { } // ❌ Doesn't work
declare module 'express-serve-static-core' { } // ✅ Actual interface location
How to check:
# Open and check node_modules/@types/express/index.d.ts
cat node_modules/@types/express/index.d.ts
2. Cannot Add Completely New Modules
// Can only augment existing modules
declare module 'existing-lib' {
interface NewInterface { } // ✅ New interface can be added
function existingFn(): void; // ✅ Overload existing function
}
3. Ensure Inclusion in tsconfig
{
"compilerOptions": {
"typeRoots": ["./node_modules/@types", "./src/types"]
},
"include": ["src/**/*.ts", "src/types/**/*.d.ts"]
}
Pro Tips
Finding the correct augmentation target for each library
# Find interface names in @types package's index.d.ts
grep -r "interface Request" node_modules/@types/express/
# → Found in express-serve-static-core module
grep -r "interface FastifyInstance" node_modules/@types/fastify/
Recommended type augmentation file structure
src/
└── types/
├── express.d.ts # Express extensions
├── env.d.ts # process.env types
├── global.d.ts # Global types
└── window.d.ts # window object extensions
Create reusable augmentation packages
Extract commonly used module augmentations into npm packages for reuse across multiple projects.