Skip to content
TypeScript ts modules 3 min read

Declaration Files (.d.ts)

Declaration files (.d.ts) describe the types of code without containing any implementation. They let TypeScript understand plain JavaScript libraries, global variables injected by a build tool, and non-code assets like JSON or CSS imports. Every TypeScript library ships .d.ts files so consumers get full type-checking and autocomplete. This page covers reading and writing declaration files, the declare keyword, ambient modules, and the @types ecosystem powered by DefinitelyTyped.

What a declaration file is

A .d.ts file contains only type information — no function bodies, no executable statements. The compiler reads it to know the shape of code that lives elsewhere (or in JavaScript). Library authors generate them automatically with "declaration": true.

// math.d.ts — describes math.js, contains no implementation
export declare function add(a: number, b: number): number;
export declare const PI: number;
export interface Vector {
  x: number;
  y: number;
}

When you publish a package, set "declaration": true (and optionally "declarationMap": true for go-to-source) so consumers get these files for free.

// tsconfig.json
{
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true,
    "outDir": "dist"
  }
}

The declare keyword

declare introduces something that exists at runtime but has no TypeScript definition — it tells the compiler “trust me, this exists” without emitting any code. This is how you describe globals provided by a script tag, a CDN, or a bundler’s define plugin.

// globals.d.ts
declare const __APP_VERSION__: string;        // injected by the bundler
declare function gtag(...args: unknown[]): void; // global analytics function

interface Window {
  dataLayer: unknown[];                         // augment the global Window
}

Now __APP_VERSION__, gtag, and window.dataLayer type-check everywhere without an import. Files containing only declare globals (no top-level import/export) are global declaration files.

A .d.ts file with a top-level import or export is a module — its declarations are scoped, not global. To declare truly global types, keep the file free of top-level imports/exports, or wrap globals in declare global { ... }.

Ambient modules

You can declare the types of an entire module — useful for untyped packages or for teaching TypeScript about non-JS imports (images, CSS, JSON handled by a bundler).

// shims.d.ts
declare module "untyped-lib" {
  export function doThing(input: string): number;
  export default class Thing {}
}

// non-code asset imports handled by a bundler
declare module "*.svg" {
  const src: string;
  export default src;
}

declare module "*.css";

A wildcard ambient module ("*.svg") matches every import of that pattern, so import logo from "./logo.svg" resolves to a string.

The @types ecosystem

Many JavaScript libraries ship without built-in types. The community maintains types for them in DefinitelyTyped, published to npm under the @types/* scope. Install the matching package and TypeScript finds it automatically.

npm install --save-dev @types/node @types/express
SituationWhat to do
Library ships its own .d.tsNothing — types just work
Types live in DefinitelyTypednpm i -D @types/<pkg>
No types anywhereWrite a local .d.ts with declare module
Wrong/outdated bundled typesPatch via module augmentation

TypeScript automatically includes @types packages found in node_modules/@types. Restrict which ones are loaded with the types compiler option when you need to.

// tsconfig.json — only load these ambient @types
{
  "compilerOptions": {
    "types": ["node", "vitest/globals"]
  }
}

Finding and shipping types

The typesVersions and exports.types fields in package.json tell consumers where your declarations live. For a single-entry library, "types" is enough; for subpath exports, add a "types" condition per entry.

// package.json for a published library
{
  "name": "my-lib",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    }
  }
}

Always point "types" at the generated .d.ts so editors and the compiler load your declarations correctly.

Best Practices

  • Enable "declaration": true when publishing a library so consumers get types.
  • Keep global declaration files free of top-level import/export, or use declare global.
  • Install community types from @types/* (DefinitelyTyped) before hand-writing them.
  • Use declare module "*.ext" to type non-code imports handled by your bundler.
  • Add declarationMap so consumers can jump to your real source, not the .d.ts.
  • Use the types compiler option to limit which ambient @types packages load.
  • Point package.json "types"/exports.types at your emitted declaration files.
Last updated June 29, 2026
Was this helpful?