TypeScriptDeclaration Files (.d.ts)

Declaration Files (.d.ts)

Declaration files (files with a .d.ts extension) contain only type information — no executable JavaScript. They describe the shape of existing JavaScript code so TypeScript can type-check it without seeing the implementation.

Every npm package that ships types either bundles its own .d.ts files or has a separate @types/package-name package on DefinitelyTyped. When you write a library in TypeScript, the compiler generates .d.ts files from your source automatically.

Why Declaration Files Exist

Consider a JavaScript library published before TypeScript existed. The distributed package contains only .js files. TypeScript has no way to know what that library exports or what types those exports have. A .d.ts file bridges this gap:

  • Library authors generate .d.ts alongside .js so consumers get types.
  • DefinitelyTyped provides community-maintained .d.ts for libraries without built-in types.
  • You write .d.ts files when integrating untyped JavaScript into a TypeScript project.
Anatomy of a Declaration File

A .d.ts file looks like regular TypeScript but with only declarations — no function bodies, no variable initializers, no class method implementations.

TS
// math-utils.d.ts

// Declare exported functions (no implementation)
export declare function add(a: number, b: number): number;
export declare function multiply(a: number, b: number): number;
export declare function clamp(value: number, min: number, max: number): number;

// Declare exported constants
export declare const PI: number;
export declare const TAU: number;

// Declare exported types (these already have no runtime presence)
export type Vector2 = { x: number; y: number };
export type Matrix2x2 = [[number, number], [number, number]];

// Declare exported classes
export declare class Complex {
  readonly real: number;
  readonly imag: number;
  constructor(real: number, imag: number);
  add(other: Complex): Complex;
  magnitude(): number;
  toString(): string;
}
      
Generating Declaration Files with tsc

TypeScript generates .d.ts files automatically when you enable the declaration compiler option. You typically also set declarationMap to generate source maps linking declarations back to the original TypeScript source.

JSON
// tsconfig.json — for a library that ships types
{
  "compilerOptions": {
    "declaration": true,         // generate .d.ts files
    "declarationMap": true,      // generate .d.ts.map files (source maps)
    "declarationDir": "./types", // output .d.ts files here (optional)
    "sourceMap": true,
    "outDir": "./dist"
  },
  "include": ["src"]
}
      
src/
  math.ts          → dist/math.js  +  types/math.d.ts
  utils/string.ts  → dist/utils/string.js  +  types/utils/string.d.ts
      
Declaration Files for a CommonJS Module

If you are typing a CommonJS library (one that uses module.exports), use export = in the declaration file:

TS
// old-lib.d.ts — types for a CJS library
declare class OldLib {
  constructor(options: OldLib.Options);
  connect(): Promise<void>;
  disconnect(): void;
}

declare namespace OldLib {
  interface Options {
    host: string;
    port: number;
    timeout?: number;
  }

  interface ConnectionEvent {
    timestamp: Date;
    host: string;
  }
}

export = OldLib;
      

TS
// Consumer — must use require-style import with export =
import OldLib = require('old-lib');
// OR with esModuleInterop:
import OldLib from 'old-lib';

const lib = new OldLib({ host: 'localhost', port: 3000 });
      
Global Declaration Files

Files that contain no import or export statements are global declaration files — their declarations add to the global TypeScript scope. Use these to type global variables injected by your environment or build process.

TS
// globals.d.ts — adds to global TypeScript scope (no import/export!)

declare const __DEV__: boolean;
declare const __APP_VERSION__: string;
declare const __BUILD_DATE__: string;

// Extend the Window interface
interface Window {
  analytics: {
    track(event: string, props?: Record<string, unknown>): void;
    identify(userId: string): void;
  };
  __REDUX_DEVTOOLS_EXTENSION__?: () => unknown;
}

// Extend NodeJS ProcessEnv
declare namespace NodeJS {
  interface ProcessEnv {
    NODE_ENV: 'development' | 'production' | 'test';
    NEXT_PUBLIC_API_URL: string;
    DATABASE_URL: string;
  }
}
      
Note
Global declaration files must not contain import or export statements at the top level. If you need to reference an existing type, use import() inline or /// <reference types="..." />.
Module Augmentation in .d.ts Files

You can extend types from existing modules without modifying them. This is module augmentation. The file must be a module (have an import or export) and use declare module with the exact module specifier.

TS
// express-extension.d.ts
import 'express';  // must be a module — import something

declare module 'express' {
  interface Request {
    user?: {
      id: string;
      email: string;
      roles: string[];
    };
    correlationId: string;
  }
}

// Now req.user and req.correlationId are typed in every Express handler
      

TS
// jest-matchers.d.ts — add custom Jest matchers
import '@jest/globals';

declare module '@jest/globals' {
  interface Matchers<R> {
    toBeWithinRange(min: number, max: number): R;
    toBeValidEmail(): R;
    toMatchSnapshot(hint?: string): R;
  }
}
      
Typing Non-JS Assets

Bundlers like webpack and Vite let you import CSS, images, SVGs, and other assets directly in TypeScript. Without declarations, TypeScript errors on these imports:

TS
// assets.d.ts — tell TypeScript how to type asset imports

// CSS modules
declare module '*.module.css' {
  const styles: Record<string, string>;
  export default styles;
}

declare module '*.module.scss' {
  const styles: Record<string, string>;
  export default styles;
}

// Image files
declare module '*.png' {
  const src: string;
  export default src;
}

declare module '*.jpg' {
  const src: string;
  export default src;
}

declare module '*.webp' {
  const src: string;
  export default src;
}

// SVG (as React component — Vite/SVGR convention)
declare module '*.svg' {
  import type { FC, SVGProps } from 'react';
  const ReactComponent: FC<SVGProps<SVGSVGElement>>;
  export default ReactComponent;
}

// Text / data files
declare module '*.txt' {
  const content: string;
  export default content;
}

declare module '*.json' {
  const data: unknown;
  export default data;
}
      
package.json: Pointing to Declaration Files

When you publish a library, package.json must tell consumers where to find the declaration files using the "types" (or "typings") field:

JSON
// package.json — library package
{
  "name": "my-utils",
  "version": "1.0.0",
  "main": "./dist/index.js",
  "module": "./dist/index.mjs",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.mjs"
      },
      "require": {
        "types": "./dist/index.d.cts",
        "default": "./dist/index.js"
      }
    }
  },
  "files": [
    "dist"
  ]
}
      
skipLibCheck and its Trade-offs

The skipLibCheck compiler option tells TypeScript to skip type-checking all .d.ts files (both your own and those in node_modules). It is enabled by default in most project templates because conflicting type declarations in node_modules can cause spurious errors.

skipLibCheck

Pros

Cons

true (common default)

Faster builds, no node_modules type conflicts

Silently ignores bad .d.ts files

false

Catches errors in .d.ts files

Can fail on conflicting @types packages

Tip
Leave skipLibCheck: true for application projects. For library authors, consider setting it to false in CI to validate that your generated .d.ts files are correct.
isolatedDeclarations (TypeScript 5.5+)

TypeScript 5.5 introduced isolatedDeclarations, which requires that every exported declaration has explicit types (no inference from other files). This enables faster parallel declaration generation — bundlers can emit .d.ts file-by-file without needing the full program.

JSON
// tsconfig.json
{
  "compilerOptions": {
    "declaration": true,
    "isolatedDeclarations": true
  }
}
      

TS
// ❌ Error with isolatedDeclarations — return type inferred from another file
export function getUser() {
  return fetchUserFromDB(); // return type depends on fetchUserFromDB's type
}

// ✓ Explicit return type — declaration can be generated from this file alone
export function getUser(): Promise<User> {
  return fetchUserFromDB();
}
      
Summary
  • .d.ts files contain only type declarations — no runtime JavaScript.

  • Enable declaration: true in tsconfig to auto-generate .d.ts from your TypeScript source.

  • Use global declaration files (no import/export) to add globals, extend Window, or type process.env.

  • Use module augmentation (declare module "x") to extend types from existing packages.

  • Asset declaration files (.png, .svg, .css) tell TypeScript how to type bundler asset imports.

  • Set the "types" field in package.json so consumers find your declarations.

  • skipLibCheck: true speeds up builds by skipping .d.ts type-checking in node_modules.