Declaration Files & Module Augmentation
Writing .d.ts files, ambient declarations, and augmenting modules
Declaration files describe the shape of JavaScript code to TypeScript. They enable type safety for libraries without TypeScript source.
.d.ts Structure
Declaration files describe types without implementation.
// math.d.ts
export function add(a: number, b: number): number
export function multiply(a: number, b: number): number
export const PI: 3.14159
// Ambient declarations (no export)
declare function parseInt(s: string, radix?: number): number
These files are picked up automatically by TypeScript when placed alongside JS files or referenced via typeRoots.
Declare Module
Add types for libraries without built-in types.
// declarations/analytics.d.ts
declare module "analytics" {
export function track(event: string, data?: Record<string, unknown>): void
export function identify(
userId: string,
traits?: Record<string, unknown>
): void
export function page(name: string): void
}
// Usage in app code
import { track } from "analytics"
track("signup", { method: "email" })
Augmenting Third-Party Modules
Extend existing module types without modifying node_modules.
// augmentations.d.ts
import "express" // import the module to augment it
declare module "express" {
interface Request {
user?: {
id: string
role: "admin" | "user"
}
}
}
// Now req.user is available everywhere
// app.get("/", (req, res) => { req.user.id })
Module augmentation uses declaration merging. The import line makes
TypeScript treat the file as a module, not a global script.
Ambient Declarations
Describe global APIs available at runtime (window, process, etc.).
// globals.d.ts
interface Window {
__APP_VERSION__: string
gtag: (command: string, id: string, params?: Record<string, unknown>) => void
}
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: "development" | "production" | "test"
API_KEY: string
DATABASE_URL: string
}
}
// Ambient variable declarations
declare const __BUILD_DATE__: string
Triple-Slash Directives
Legacy way to reference other type files. Still used in some libraries.
/// <reference types="node" />
/// <reference path="./types.d.ts" />
/// <reference lib="es2020" />
// <reference types> — ambient module dependency
// <reference path> — relative file declaration
// <reference lib> — built-in library (es2015, dom, etc.)
Modern code uses tsconfig.json instead:
compilerOptions.typesfor type packagescompilerOptions.typeRootsfor custom declaration directories
Publishing Types with Packages
// Types published alongside JS in an npm package
// package.json
{
"name": "@company/utils",
"main": "./dist/index.js",
"types": "./dist/index.d.ts", // entry point for types
"files": ["dist"]
}
// Or use typesVersions for different TS versions
{
"typesVersions": {
">=4.0": { "*": ["dist/ts4.0/*"] },
">=5.0": { "*": ["dist/ts5.0/*"] }
}
}
Real-World: Declaring a Plain JS Library
// Given this JS library (no types provided):
// localStorage-db.js — simple key-value store with TTL
// db.set(key, value, ttlSeconds)
// db.get(key) — returns null if expired
// db.delete(key) — returns boolean
// db.clear() — removes all
// db.keys() — returns string[]
// Declaration file: localStorage-db.d.ts
declare module "local-storage-db" {
interface DBOptions {
prefix?: string
}
export function createDB(name: string, options?: DBOptions): DB
export interface DB {
set<T>(key: string, value: T, ttlSeconds?: number): void
get<T = unknown>(key: string): T | null
delete(key: string): boolean
clear(): void
keys(): string[]
}
}
1// Imagine this is a .d.ts file2// Create types for this JS library:3// const logger = createLogger("myapp", { level: "info" })4// logger.log("hello")5// logger.warn("be careful")6// logger.error("something broke {user}", { user: "alice" })7// logger.setLevel("debug")8 9// Write your declaration:10declare module "simple-logger" {11 12}