|$ curl https://forge-ai.dev/api/markdown?path=docs/typescript/declarations
$cat docs/typescript-—-declaration-files.md
updated Recently·14 min read·published

TypeScript — Declaration Files

TypeScriptAdvanced
Introduction

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 declare Keyword

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.

declare_keyword.ts
TypeScript
1// declare a variable that exists at runtime
2declare const API_URL: string;
3declare const VERSION: number;
4
5// declare a function implemented elsewhere
6declare function fetchJSON(url: string): Promise<unknown>;
7
8// declare a class (only the type shape, no implementation)
9declare 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)
16declare enum LogLevel {
17 DEBUG = "DEBUG",
18 INFO = "INFO",
19 WARN = "WARN",
20 ERROR = "ERROR",
21}
22
23// declare a namespace
24declare namespace Config {
25 const apiUrl: string;
26 function getTimeout(): number;
27}
28
29// Use them — TypeScript trusts these exist
30console.log(API_URL);
31const emitter = new EventEmitter();
32emitter.on("data", (chunk) => console.log(chunk));
33const level = LogLevel.INFO;
34const timeout = Config.getTimeout();

info

The declare keyword can also be used with const, let, function, class, enum, and namespace. In a .d.ts file, the declare keyword is optional — everything is implicitly ambient.
Ambient Variable Declarations

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.

ambient.d.ts
TypeScript
1// globals.d.ts — ambient declarations for your app
2
3// Environment variables from .env files
4declare 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)
14declare const __APP_VERSION__: string;
15declare const __BUILD_TIME__: string;
16
17// Feature flags
18declare const FEATURE_FLAGS: {
19 readonly darkMode: boolean;
20 readonly betaFeatures: boolean;
21 readonly maxUploadSize: number;
22};
23
24// Typed process.env for Node.js
25declare 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
35function 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}
Ambient Module Declarations

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.

module_declarations.d.ts
TypeScript
1// Declare a module without types
2declare 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)
9declare module "*.css" {
10 const classes: Record<string, string>;
11 export default classes;
12}
13
14declare module "*.svg" {
15 const content: string;
16 export default content;
17}
18
19declare module "*.png" {
20 const content: string;
21 export default content;
22}
23
24// Module declaration for JSON imports
25declare module "*.json" {
26 const value: Record<string, unknown>;
27 export default value;
28}
29
30// Declare a module with augmentation
31declare 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
45import styles from "./Component.css";
46import logo from "./logo.svg";
47import config from "./config.json";

best practice

For npm packages, check if @types/PackageName exists on DefinitelyTyped before writing your own declarations. For custom file imports (.css, .svg, .png), wildcard module declarations keep your codebase type-safe.
Global Augmentations

Global augmentations extend existing global types (like Window, Document, or Array) by adding new properties or methods.

global_augment.d.ts
TypeScript
1// Extend the global Window interface
2export {};
3
4declare 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
32window.analytics.track("button_clicked", { id: "submit" });
33
34const nums = [1, 2, 3, 4, 5];
35nums.first(); // number | undefined
36nums.last(); // number | undefined
37nums.groupBy(n => n % 2 === 0 ? "even" : "odd");
38// { even: [2, 4], odd: [1, 3, 5] }
Module Augmentation

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.

module_augment.d.ts
TypeScript
1// augment-react.d.ts — extend React's types
2
3// Must re-export to make it a module
4import "react";
5
6declare 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
18import "express";
19
20declare 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
36import "zod";
37
38declare 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
45app.get("/profile", (req, res) => {
46 if (req.user) {
47 console.log(req.user.email); // typed as string
48 }
49});

warning

Module augmentation only works if the module has at least one import or export. A plain declare module without re-exporting creates a new module declaration, not an augmentation. Always include import "existing-module" at the top.
Triple-Slash Directives

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.

triple_slash.ts
TypeScript
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
26declare namespace NodeJS {
27 interface ProcessEnv {
28 NODE_ENV: "development" | "production";
29 }
30}
31
32declare const __dirname: string;
33declare const __filename: string;
Publishing Type Definitions

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.

package.json
JSON
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}
lib.ts
TypeScript
1// lib/index.ts — source that generates .d.ts
2export interface Config {
3 apiUrl: string;
4 timeout: number;
5 retries: number;
6}
7
8export function createClient(config: Config): {
9 get<T>(path: string): Promise<T>;
10 post<T>(path: string, body: unknown): Promise<T>;
11};
12
13export 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;
Using DefinitelyTyped

@types/* packages from DefinitelyTyped provide community-maintained type definitions for thousands of JavaScript libraries.

terminal
Bash
1# Install types for a library
2npm install --save-dev @types/lodash
3npm install --save-dev @types/express
4npm 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
10npm search @types/my-library
11
12# If no types exist, create a declaration file
13# src/types/my-library.d.ts
14declare module "my-library" {
15 export function doStuff(input: string): number;
16}
17
18# Or use @types/my-library if someone already contributed them

info

Many modern libraries ship their own TypeScript types. Check the library's package.json for a types or typings field before installing @types/* packages — you may already have types bundled in.
Best Practices

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.

$Blueprint — Engineering Documentation·Section ID: TS-DECL·Revision: 1.0