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.tsalongside.jsso consumers get types. - DefinitelyTyped provides community-maintained
.d.tsfor libraries without built-in types. - You write
.d.tsfiles 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.
// 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.
// 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:
// 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;
// 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.
// 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;
}
}
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.
// 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
// 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:
// 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:
// 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 |
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.
// tsconfig.json
{
"compilerOptions": {
"declaration": true,
"isolatedDeclarations": true
}
}
// ❌ 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.