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.tsfile with a top-levelimportorexportis 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 indeclare 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
| Situation | What to do |
|---|---|
Library ships its own .d.ts | Nothing — types just work |
| Types live in DefinitelyTyped | npm i -D @types/<pkg> |
| No types anywhere | Write a local .d.ts with declare module |
| Wrong/outdated bundled types | Patch 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": truewhen publishing a library so consumers get types. - Keep global declaration files free of top-level
import/export, or usedeclare 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
declarationMapso consumers can jump to your real source, not the.d.ts. - Use the
typescompiler option to limit which ambient@typespackages load. - Point
package.json"types"/exports.typesat your emitted declaration files.