← thecodex.expert · The Codex Family of Knowledge
TypeScript

Declaration Files (.d.ts)

A .d.ts file has zero runtime code inside it — it exists purely to describe a shape, letting a plain JavaScript library get full TypeScript autocomplete and checking.

TypeScript 6.0 @types/* packages Last verified:
Where this bites you

declare means "trust me", and TypeScript does. Nothing compares a .d.ts file with the JavaScript it describes, so a declaration that says number where the library actually returns a string is accepted everywhere. Your code then type-checks against the declaration, not the behaviour: here a variable typed number ends up holding the string "51", with no error at compile time or run time.

TypeScriptprice.js + price.d.ts + main.ts
// price.js -- the real library
export function parsePrice(text) {
    return text.replace("$", "").trim();   // returns a string
}

// price.d.ts -- a wrong declaration
export declare function parsePrice(text: string): number;

// main.ts
import { parsePrice } from "./price.js";

const total: number = parsePrice("$5") + 1;
console.log(total);
// tsc --strict: no errors
// node: 51      ("5" + 1, while TypeScript believes total is a number)
Canonical Definition

A .d.ts file contains only type declarations — function signatures, interfaces, variable types — with no actual implementation. It lets a JavaScript library (which has no types of its own) be used from TypeScript with full autocomplete and compile-time checking. The community-maintained DefinitelyTyped project publishes these as @types/* npm packages for thousands of popular untyped JavaScript libraries.

A basic declaration file

declare tells TypeScript "this exists at runtime, trust me — here's its type" without providing (or requiring) an implementation in this file.

TypeScriptmylib.d.ts
declare function greet(name: string): string;

declare interface Config {
    apiKey: string;
    timeout?: number;
}

declare const VERSION: string;

The same trust applies to @types packages below: they are written by hand, separately from the library, and can lag behind or simply be wrong. When behaviour and types disagree, believe the behaviour and fix the declaration. Once it says string, the same line becomes a compile error (error TS2322: Type 'string' is not assignable to type 'number'.), and the real conversion has to be written:

TypeScriptprice.d.ts + main.ts (fixed)
// price.d.ts
export declare function parsePrice(text: string): string;

// main.ts
import { parsePrice } from "./price.js";

const total: number = Number(parsePrice("$5")) + 1;
console.log(total);   // 6

@types packages: types for existing JavaScript libraries

Installing an @types/* package adds nothing to your runtime bundle — it's a devDependency containing only .d.ts files, which TypeScript reads at compile time and then discards.

Shellterminal
$ npm install lodash              # the actual JavaScript library
$ npm install -D @types/lodash    # its community-maintained type declarations
TypeScriptuse_lodash.ts
// now this has full autocomplete and type-checking, even though
// lodash itself is plain untyped JavaScript:
import _ from "lodash";
_.chunk([1, 2, 3, 4], 2);

Writing your own for an untyped library

When no @types package exists for a library, a minimal ambient declaration (even just declare module "library-name";, treating it as any) unblocks usage, and can be refined incrementally with real type information as needed.

TypeScriptcustom.d.ts
declare module "some-untyped-library" {
    export function doSomething(input: string): number;
}

Sources

1
Microsoft. TypeScript Handbook, "Type Declarations", typescriptlang.org/docs/handbook/declaration-files, and DefinitelyTyped, github.com/DefinitelyTyped.