TypeScript — Declaration Files
Declaration files (.d.ts) describe the shape of JavaScript modules and global values. They contain no runtime code — only type information. They enable TypeScript to type-check code that was written in plain JavaScript, third-party libraries, or environment-specific APIs.
The declarekeyword tells TypeScript that something exists at runtime but shouldn't be emitted in the compiled output. It's the foundation of all ambient declarations.
| 1 | // declare a variable that exists at runtime |
| 2 | declare const API_URL: string; |
| 3 | declare const VERSION: number; |
| 4 | |
| 5 | // declare a function implemented elsewhere |
| 6 | declare function fetchJSON(url: string): Promise<unknown>; |
| 7 | |
| 8 | // declare a class (only the type shape, no implementation) |
| 9 | declare class EventEmitter { |
| 10 | on(event: string, listener: (...args: unknown[]) => void): void; |
| 11 | emit(event: string, ...args: unknown[]): void; |
| 12 | off(event: string, listener: (...args: unknown[]) => void): void; |
| 13 | } |
| 14 | |
| 15 | // declare an enum (ambient enum — no runtime emission) |
| 16 | declare enum LogLevel { |
| 17 | DEBUG = "DEBUG", |
| 18 | INFO = "INFO", |
| 19 | WARN = "WARN", |
| 20 | ERROR = "ERROR", |
| 21 | } |
| 22 | |
| 23 | // declare a namespace |
| 24 | declare namespace Config { |
| 25 | const apiUrl: string; |
| 26 | function getTimeout(): number; |
| 27 | } |
| 28 | |
| 29 | // Use them — TypeScript trusts these exist |
| 30 | console.log(API_URL); |
| 31 | const emitter = new EventEmitter(); |
| 32 | emitter.on("data", (chunk) => console.log(chunk)); |
| 33 | const level = LogLevel.INFO; |
| 34 | const timeout = Config.getTimeout(); |
info
Ambient variables describe values that exist in the runtime environment but TypeScript doesn't know about. Common use cases include environment variables, global constants, and browser/Node.js globals.
| 1 | // globals.d.ts — ambient declarations for your app |
| 2 | |
| 3 | // Environment variables from .env files |
| 4 | declare const process: { |
| 5 | env: { |
| 6 | NODE_ENV: "development" | "production" | "test"; |
| 7 | API_URL: string; |
| 8 | DATABASE_URL: string; |
| 9 | DEBUG?: string; |
| 10 | }; |
| 11 | }; |
| 12 | |
| 13 | // Global constants injected by bundler (Vite, webpack) |
| 14 | declare const __APP_VERSION__: string; |
| 15 | declare const __BUILD_TIME__: string; |
| 16 | |
| 17 | // Feature flags |
| 18 | declare const FEATURE_FLAGS: { |
| 19 | readonly darkMode: boolean; |
| 20 | readonly betaFeatures: boolean; |
| 21 | readonly maxUploadSize: number; |
| 22 | }; |
| 23 | |
| 24 | // Typed process.env for Node.js |
| 25 | declare namespace NodeJS { |
| 26 | interface ProcessEnv { |
| 27 | NODE_ENV: "development" | "production" | "test"; |
| 28 | PORT?: string; |
| 29 | DATABASE_URL: string; |
| 30 | REDIS_URL?: string; |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | // Now you can use them with full type safety |
| 35 | function initApp() { |
| 36 | console.log(`Version: ${__APP_VERSION__}`); |
| 37 | console.log(`Env: ${process.env.NODE_ENV}`); |
| 38 | |
| 39 | if (FEATURE_FLAGS.darkMode) { |
| 40 | enableDarkMode(); |
| 41 | } |
| 42 | } |
declare module describes the shape of a module that has no type declarations. This is how you type plain JavaScript libraries or custom file imports.
| 1 | // Declare a module without types |
| 2 | declare module "untyped-library" { |
| 3 | export function doSomething(input: string): number; |
| 4 | export function doOtherThing(input: number): string; |
| 5 | export default function main(): void; |
| 6 | } |
| 7 | |
| 8 | // Wildcard module declaration (catch-all for a pattern) |
| 9 | declare module "*.css" { |
| 10 | const classes: Record<string, string>; |
| 11 | export default classes; |
| 12 | } |
| 13 | |
| 14 | declare module "*.svg" { |
| 15 | const content: string; |
| 16 | export default content; |
| 17 | } |
| 18 | |
| 19 | declare module "*.png" { |
| 20 | const content: string; |
| 21 | export default content; |
| 22 | } |
| 23 | |
| 24 | // Module declaration for JSON imports |
| 25 | declare module "*.json" { |
| 26 | const value: Record<string, unknown>; |
| 27 | export default value; |
| 28 | } |
| 29 | |
| 30 | // Declare a module with augmentation |
| 31 | declare module "express" { |
| 32 | import { Request, Response, NextFunction } from "express"; |
| 33 | |
| 34 | interface Application { |
| 35 | get(path: string, handler: (req: Request, res: Response) => void): Application; |
| 36 | post(path: string, handler: (req: Request, res: Response) => void): Application; |
| 37 | use(handler: (req: Request, res: Response, next: NextFunction) => void): Application; |
| 38 | } |
| 39 | |
| 40 | function express(): Application; |
| 41 | export default express; |
| 42 | } |
| 43 | |
| 44 | // Usage — TypeScript now understands these imports |
| 45 | import styles from "./Component.css"; |
| 46 | import logo from "./logo.svg"; |
| 47 | import config from "./config.json"; |
best practice
Global augmentations extend existing global types (like Window, Document, or Array) by adding new properties or methods.
| 1 | // Extend the global Window interface |
| 2 | export {}; |
| 3 | |
| 4 | declare global { |
| 5 | interface Window { |
| 6 | analytics: { |
| 7 | track(event: string, properties?: Record<string, unknown>): void; |
| 8 | identify(userId: string): void; |
| 9 | }; |
| 10 | __REDUX_DEVTOOLS_EXTENSION__?: { |
| 11 | connect(): unknown; |
| 12 | }; |
| 13 | } |
| 14 | |
| 15 | // Extend Array with a custom method |
| 16 | interface Array<T> { |
| 17 | first(): T | undefined; |
| 18 | last(): T | undefined; |
| 19 | groupBy<K extends string>( |
| 20 | fn: (item: T) => K |
| 21 | ): Record<K, T[]>; |
| 22 | } |
| 23 | |
| 24 | // Extend the global Date |
| 25 | interface DateConstructor { |
| 26 | now(): number; |
| 27 | fromISO(iso: string): Date; |
| 28 | } |
| 29 | } |
| 30 | |
| 31 | // Usage — all these are now typed globally |
| 32 | window.analytics.track("button_clicked", { id: "submit" }); |
| 33 | |
| 34 | const nums = [1, 2, 3, 4, 5]; |
| 35 | nums.first(); // number | undefined |
| 36 | nums.last(); // number | undefined |
| 37 | nums.groupBy(n => n % 2 === 0 ? "even" : "odd"); |
| 38 | // { even: [2, 4], odd: [1, 3, 5] } |
Module augmentation extends the types of an existing module. This is useful when you want to add types to a third-party library without modifying its source code.
| 1 | // augment-react.d.ts — extend React's types |
| 2 | |
| 3 | // Must re-export to make it a module |
| 4 | import "react"; |
| 5 | |
| 6 | declare module "react" { |
| 7 | // Add a new hook |
| 8 | function useLocalStorage<T>( |
| 9 | key: string, |
| 10 | initialValue: T |
| 11 | ): [T, (value: T | ((prev: T) => T)) => void]; |
| 12 | |
| 13 | // Extend existing hook return type |
| 14 | // (React 18+ — this is just an example pattern) |
| 15 | } |
| 16 | |
| 17 | // Augment Express with custom middleware |
| 18 | import "express"; |
| 19 | |
| 20 | declare module "express" { |
| 21 | interface Request { |
| 22 | user?: { |
| 23 | id: string; |
| 24 | email: string; |
| 25 | role: "admin" | "user"; |
| 26 | }; |
| 27 | sessionId?: string; |
| 28 | } |
| 29 | |
| 30 | interface Response { |
| 31 | json<T>(body: T): Response; |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | // Augment a library's types |
| 36 | import "zod"; |
| 37 | |
| 38 | declare module "zod" { |
| 39 | interface ZodType { |
| 40 | or<T extends z.ZodTypeAny>(this: T, other: z.ZodType): z.ZodUnion<[T, typeof other]>; |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | // Now Express routes have req.user typed |
| 45 | app.get("/profile", (req, res) => { |
| 46 | if (req.user) { |
| 47 | console.log(req.user.email); // typed as string |
| 48 | } |
| 49 | }); |
warning
Triple-slash directives are XML-like comments that tell the compiler to include other files or set compiler options. They're primarily used to reference type libraries.
| 1 | // Reference a type definition file |
| 2 | /// <reference path="./globals.d.ts" /> |
| 3 | /// <reference types="node" /> |
| 4 | /// <reference types="jest" /> |
| 5 | |
| 6 | // Reference a library by name |
| 7 | /// <reference types="node" /> |
| 8 | |
| 9 | // Set compiler options (rarely needed — use tsconfig) |
| 10 | /// <reference lib="es2020" /> |
| 11 | /// <reference lib="dom" /> |
| 12 | |
| 13 | // AMD module dependency (legacy) |
| 14 | /// <amd-dependency path="jquery" /> |
| 15 | |
| 16 | // Modern usage (mostly obsolete — use imports instead) |
| 17 | // triple-slash directives are mainly useful in: |
| 18 | // 1. .d.ts files that need to reference other type files |
| 19 | // 2. Single-file scripts that can't use tsconfig |
| 20 | // 3. Type libraries that depend on other type libraries |
| 21 | |
| 22 | // Example: a types file that references Node types |
| 23 | // env.d.ts |
| 24 | /// <reference types="node" /> |
| 25 | |
| 26 | declare namespace NodeJS { |
| 27 | interface ProcessEnv { |
| 28 | NODE_ENV: "development" | "production"; |
| 29 | } |
| 30 | } |
| 31 | |
| 32 | declare const __dirname: string; |
| 33 | declare const __filename: string; |
When publishing a TypeScript package, you need to include type definitions so consumers get type safety. There are two approaches: bundled types or a separate @types package.
| 1 | { |
| 2 | "name": "my-library", |
| 3 | "version": "1.0.0", |
| 4 | "main": "./dist/index.js", |
| 5 | "types": "./dist/index.d.ts", |
| 6 | "exports": { |
| 7 | ".": { |
| 8 | "types": "./dist/index.d.ts", |
| 9 | "import": "./dist/index.mjs", |
| 10 | "require": "./dist/index.cjs" |
| 11 | } |
| 12 | }, |
| 13 | "files": ["dist"], |
| 14 | "sideEffects": false |
| 15 | } |
| 1 | // lib/index.ts — source that generates .d.ts |
| 2 | export interface Config { |
| 3 | apiUrl: string; |
| 4 | timeout: number; |
| 5 | retries: number; |
| 6 | } |
| 7 | |
| 8 | export function createClient(config: Config): { |
| 9 | get<T>(path: string): Promise<T>; |
| 10 | post<T>(path: string, body: unknown): Promise<T>; |
| 11 | }; |
| 12 | |
| 13 | export default createClient; |
| 14 | |
| 15 | // After building with "declaration": true, |
| 16 | // dist/index.d.ts is automatically generated: |
| 17 | // |
| 18 | // export interface Config { |
| 19 | // apiUrl: string; |
| 20 | // timeout: number; |
| 21 | // retries: number; |
| 22 | // } |
| 23 | // export declare function createClient(config: Config): { |
| 24 | // get<T>(path: string): Promise<T>; |
| 25 | // post<T>(path: string, body: unknown): Promise<T>; |
| 26 | // }; |
| 27 | // export default createClient; |
@types/* packages from DefinitelyTyped provide community-maintained type definitions for thousands of JavaScript libraries.
| 1 | # Install types for a library |
| 2 | npm install --save-dev @types/lodash |
| 3 | npm install --save-dev @types/express |
| 4 | npm install --save-dev @types/node |
| 5 | |
| 6 | # Types are automatically found by tsc |
| 7 | # No need to import — they're in node_modules/@types/ |
| 8 | |
| 9 | # Check if types exist |
| 10 | npm search @types/my-library |
| 11 | |
| 12 | # If no types exist, create a declaration file |
| 13 | # src/types/my-library.d.ts |
| 14 | declare module "my-library" { |
| 15 | export function doStuff(input: string): number; |
| 16 | } |
| 17 | |
| 18 | # Or use @types/my-library if someone already contributed them |
info
Declaration files are a critical part of the TypeScript ecosystem. Follow these patterns to keep them accurate and maintainable.
Keep declarations minimal. Only declare what consumers actually need. Over-declaring creates maintenance burden and can mask runtime errors.
Use module augmentation over globals. Prefer declare module with augmentation over global declare global. Globals pollute the namespace and cause conflicts.
Always add export for module augmentation.Without it, TypeScript treats the file as a script (global scope) and augmentation won't work correctly.
Test your types. Use tsd or expect-type to write type-level tests that verify your declarations are correct.
Prefer bundled types over @types.If you publish a TypeScript library, include the .d.ts files in your package. It's simpler for consumers and avoids version drift between code and types.