TypeScript Declaration Files
Parent: TypeScript Expert · Published reference · snapshot 2026-09-08 · skill lang-js-ts/references/typescript-declaration-files.md
↓ Facts as markdownall context files
A lang-js-ts hub reference for producing and shipping TypeScript type information: how .d.ts
These notes link each claim to its source. Some citations identify archived research reports that are no longer hosted on this site. A published reference means the content is available; it does not certify independent review or accuracy.Read the editorial policy and follow the sources before relying on a claim.
TypeScript Declaration Files (.d.ts Authoring & Type Distribution)
- A lang-js-ts hub reference for producing and shipping TypeScript type information: how .d.ts files are emitted, how to hand-author ambient declarations and augment other people's types, and how to distribute types from an npm package so downstream consumers (and tsc, attw, editors) resolve them correctly across ESM and CJS. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#typescript-declaration-files-dts-authoring-type-distribution]
- A .d.ts is types only - no runtime code, no JS output. It is the contract tsc reads when it can't see a library's source. Scope discipline: declaration emit compiler options live here (not in typescript-compiler-config); the runtime resolution algorithm that finds the file lives in nodejs-module-resolution; the bundler that produces the file lives in nodejs-build-tooling-bundlers; advanced type operators used inside a .d.ts live in typescript-advanced-types. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#typescript-declaration-files-dts-authoring-type-distribution]
Overview
- There are two ways a .d.ts comes into existence: emitted by the compiler from .ts source (declaration: true), or hand-authored as an ambient declaration for code TypeScript can't analyze (plain JS libs, globals injected by a <script>, env vars). Distribution is the third axis: a package points consumers at its types via the types field and the exports map, or - if it ships no types - the community publishes them under @types/* via DefinitelyTyped. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#overview]
- Version anchors (memorize - these drive "does my TS have X" questions): [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#overview]
1. Declaration emit — turning `.ts` into `.d.ts`
- With declaration: true, tsc emits a .d.ts next to each .js it produces. The four knobs: [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#1-declaration-emit-turning-ts-into-dts]
- Why explicit return types help emit (this is load-bearing). When a function lacks an annotated return type, the emitter must infer the type and write it into the .d.ts. Inference can be slow on complex code, and worse, it can produce a declaration that references a symbol the consumer can't name - TS then errors with TS2742 ("inferred type cannot be named without a reference to …") or TS4082 ("default export of the module has or is using private name …"). Annotating the public surface (exported function/method return types, exported const types) makes emit a near-mechanical copy and sidesteps the un-nameable-symbol class of failures entirely. This is exactly what isolated declarations formalizes. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#1-declaration-emit-turning-ts-into-dts]
2. `--isolatedDeclarations` (TS 5.5)
- isolatedDeclarations: true forces the public API to be explicitly typed enough that a .d.ts can be generated from a single file without consulting any other file. That one-in/one-out property lets non-tsc tools (oxc, swc, esbuild's experimental path) emit declarations in parallel, per file, which tsc's whole-program declaration emit can't do. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#2---isolateddeclarations-ts-55]
- Requires declaration: true (or composite: true) - it's a stricter mode of declaration emit, so the compiler errors if neither is set. (isolatedModules is a recommended companion for the same "per-file" philosophy, but it is not the enforced prerequisite - declaration/composite is.) [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#2---isolateddeclarations-ts-55]
- What it enforces (representative error: TS9007, "Function must have an explicit return type annotation with --isolatedDeclarations"; the TS900x/TS903x family covers the other inference-blocking cases): [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#2---isolateddeclarations-ts-55]
- Every exported function / arrow / function-expression assigned to an exported binding needs an explicit return type. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#2---isolateddeclarations-ts-55]
- Exported let/const/var need an annotation or a trivially-inferable literal initializer. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#2---isolateddeclarations-ts-55]
- Public and protected class members (fields, accessors, method return types) need annotations. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#2---isolateddeclarations-ts-55]
- Constructs whose emitted type can't be computed locally are rejected (e.g. spreading a value whose type comes from another file, computed property keys whose type needs inference). [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#2---isolateddeclarations-ts-55]
3. Hand-authoring `.d.ts` — the global-vs-module rule
- The single most important authoring fact: **a .d.ts with any top-level import or export is a module - its declarations are scoped, not global. A .d.ts with no** top-level import/export is a script: every declared name lands in the global scope. moduleDetection is always auto for .d.ts, so you cannot force this with config - it's purely structural. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#3-hand-authoring-dts-the-global-vs-module-rule]
- To put global types in a file that is a module (has imports/exports), wrap them in declare global: [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#3-hand-authoring-dts-the-global-vs-module-rule]
- Triple-slash directives (must be at the very top, before any statement): [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#3-hand-authoring-dts-the-global-vs-module-rule]
- In published declaration files, use /// <reference types="..." /> to declare a dependency on another package's types; do not use /// <reference path="..." /> (the TS team explicitly flags it as a red flag - it bakes in a relative file layout). Prefer real imports where the file is a module. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#3-hand-authoring-dts-the-global-vs-module-rule]
4. Declaration merging & module augmentation
- TypeScript merges multiple declarations of the same name in the same scope. This is the mechanism behind extending types you don't own. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#4-declaration-merging-module-augmentation]
- Interface merging - same-named interfaces combine their members: [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#4-declaration-merging-module-augmentation]
- Namespace + function/class/enum merging - a namespace merges with a same-named function, class, or enum, letting you hang static-like members off a callable/constructable: [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#4-declaration-merging-module-augmentation]
- Module augmentation - re-open another package's module from a file that is itself a module and declare module "their-pkg". The augmenting declarations merge into the original; you cannot add new top-level exports this way, only augment existing shapes. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#4-declaration-merging-module-augmentation]
- Augmenting globals from inside a module uses declare global. The two canonical Node patterns: [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#4-declaration-merging-module-augmentation]
5. Shipping types from a package
- Bundled (most common): emit .d.ts with your build and point at the entry declaration. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#5-shipping-types-from-a-package]
- With an exports map (modern, and node16/nodenext resolution requires it): add a types condition. It MUST appear first in each condition block - TypeScript reads conditions top-to-bottom and stops at the first match, so a types placed after import/require/default is silently ignored. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#5-shipping-types-from-a-package]
- Dual-package types (the gotcha): when you ship both ESM and CJS, a single index.d.ts is wrong if your package is "type": "module" - under require, the consumer's compiler sees the .d.ts as ESM syntax describing a CJS file. Ship two declaration files: .d.mts for the import condition and .d.cts for the require condition (or index.d.ts + index.d.cts). This is precisely what @arethetypeswrong/cli flags as "Masquerading as CJS/ESM." [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#5-shipping-types-from-a-package]
- typesVersions - serve different declarations to older TypeScript versions, or remap subpaths: [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#5-shipping-types-from-a-package]
- UMD global (export as namespace) - for a library usable both as a module and as a <script> global: [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#5-shipping-types-from-a-package]
- export as namespace id is only legal in a .d.ts that also has other top-level exports; placing it in a .ts errors with TS1315. It only takes effect when the file enters compilation via a triple-slash reference or as a top-level input - it's a no-op when the package is imported. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#5-shipping-types-from-a-package]
Tools / Frameworks
- tsc - tsc --emitDeclarationOnly (types-only build), tsc -b (project references; needs composite: true, which implies declaration: true). [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#tools-frameworks]
- @arethetypeswrong/cli (attw) - attw --pack . or attw <tarball> validates that published types resolve under every module/condition combination. Flags "Masquerading as CJS/ESM," "Fallback Condition," "No types," "missing package.json exports." Run in CI before publish. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#tools-frameworks]
- tsd - type-level test runner (uses expect-type under the hood). Write *.test-d.ts files with expectType<T>(value) and expectError(...); tsd runs the compiler and asserts. Configure via a tsd block in package.json. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#tools-frameworks]
- Vitest type testing - alternative to tsd: *.test-d.ts with expectTypeOf(x).toEqualTypeOf<T>() or the simpler assertType<T>(x); gated behind typecheck config (statically analyzed, never run). [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#tools-frameworks]
- **@types/* + DefinitelyTyped (DT)** - the community type registry, auto-published to the @types npm org from the DT monorepo. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#tools-frameworks]
Methodology
- Emit vs. hand-author. If you own the .ts source, prefer emitted .d.ts (declaration: true) - never hand-maintain types that duplicate your source. Hand-author only for untyped JS deps, ambient globals, or non-code imports. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#methodology]
- Annotate the public surface (exported return types, exported const types). Consider turning on isolatedDeclarations: true to enforce it and unlock parallel/third-party d.ts emit. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#methodology]
- Decide global vs. module per file by the top-level import/export rule; reach for declare global only inside module files; reach for declare module "x" to augment a dependency. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#methodology]
- Wire distribution: types field for the simple case; an exports map with a leading types condition for node16/nodenext; split .d.mts/.d.cts for dual packages. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#methodology]
- Validate before publish: attw --pack . for resolution correctness, tsd/Vitest for the type contract. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#methodology]
Consuming & contributing `@types` / DefinitelyTyped
- Consuming. tsc auto-includes every @types/* package found under node_modules/@types (and parent node_modules). Two tsconfig knobs scope this: [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#consuming-contributing-types-definitelytyped]
- typeRoots - which folders hold ambient type packages (default ["./node_modules/@types"]). [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#consuming-contributing-types-definitelytyped]
- types - an allow-list; "types": ["node", "jest"] includes only those, excluding all other @types/* from the global scope. Use it to stop unrelated global types (e.g. a stray @types/mocha) from polluting a project. (Package-scoped, imported types are unaffected - this gates only the automatically-included ambient packages.) [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#consuming-contributing-types-definitelytyped]
- Contributing a NEW types package to DefinitelyTyped. Create types/foo/ with these files (the old triple-slash header comment is gone - metadata now lives in a package.json): [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#consuming-contributing-types-definitelytyped]
- Tests use dtslint assertions inside foo-tests.ts: // $ExpectType string on the line above an expression, and // @ts-expect-error for code that must fail to compile. Validate locally with pnpm test (DT runs dtslint + attw); after merge, the @types/foo package publishes automatically within a few hours. Declare runtime-type dependencies in dependencies (not devDependencies) so consumers pull them transitively. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#consuming-contributing-types-definitelytyped]
Anti-Patterns
- Putting the types condition after import/require/default in exports - TS stops at the first match and never sees it. types goes first. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#anti-patterns]
- Shipping one index.d.ts for a dual ESM+CJS "type": "module" package - "Masquerading as CJS." Ship .d.mts + .d.cts. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#anti-patterns]
- Hand-maintaining .d.ts that mirror your own .ts source instead of emitting them - they drift. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#anti-patterns]
- /// <reference path="..." /> in published types - bakes in a file layout; use /// <reference types="..." /> or real imports. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#anti-patterns]
- Exporting un-annotated functions from a library entry and being surprised by TS2742/TS4082 ("cannot be named") on emit - annotate the public surface. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#anti-patterns]
- Expecting declare module "x" (module augmentation) to add new top-level exports - it can only augment existing shapes; the augmenting file must itself be a module. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#anti-patterns]
- Using let/const to declare a global variable in a declare global block - use var. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#anti-patterns]
- Relying on tsconfig paths to be honored in published types - consumers don't share your paths; emit fully-resolved specifiers (the Node-native runner ignores paths too, per the sibling native-strip-types reference). [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#anti-patterns]
Troubleshooting
- error TS9007 (or TS900x) under isolatedDeclarations → add the explicit return type / annotation it points at. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#troubleshooting]
- isolatedDeclarations can only be used when … declaration … is enabled → set declaration: true or composite: true. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#troubleshooting]
- TS2742 / TS4082 "cannot be named" on emit → annotate the export, or export the referenced symbol so it's nameable. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#troubleshooting]
- Consumer "Could not find a declaration file for module 'foo'" → ship a types field / types condition, or npm i -D @types/foo, or write a local declare module "foo" stub. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#troubleshooting]
- attw "Masquerading as CJS/ESM" / "Fallback Condition" → fix the exports conditions; provide the matching .d.mts/.d.cts for each runtime entry. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#troubleshooting]
- declare global "Augmentations for the global scope can only be nested in … modules" → the file isn't a module; add export {};. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#troubleshooting]
- TS1315 "Global module exports may only appear in declaration files" → export as namespace is in a .ts; move it to a .d.ts. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#troubleshooting]
- Global types from a .d.ts don't appear → the file has a top-level import/export, so it's a module; either remove them or wrap the globals in declare global. [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#troubleshooting]
References
- TS Handbook - Declaration Files (Introduction / Library Structures / Templates: module, global, global-plugin, module-plugin): https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#references]
- TS Handbook - Declaration Merging: https://www.typescriptlang.org/docs/handbook/declaration-merging.html [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#references]
- TS Handbook - Modules (module vs. script rule): https://www.typescriptlang.org/docs/handbook/2/modules.html [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#references]
- TS Handbook - Publishing: https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#references]
- TSConfig - isolatedDeclarations / declaration / declarationMap / emitDeclarationOnly / declarationDir: https://www.typescriptlang.org/tsconfig/#isolatedDeclarations [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#references]
- TS 5.5 release notes (isolatedDeclarations): https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#references]
- microsoft/TypeScript #58944 - Isolated Declarations: state of the feature: https://github.com/microsoft/TypeScript/issues/58944 [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#references]
- DefinitelyTyped README + contribution guide: https://github.com/DefinitelyTyped/DefinitelyTyped / https://definitelytyped.org/guides/contributing.html [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#references]
- @arethetypeswrong/cli (problem kinds: FalseCJS/FalseESM/FallbackCondition): https://github.com/arethetypeswrong/arethetypeswrong.github.io [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#references]
- tsd: https://github.com/tsdjs/tsd ; Vitest "Testing Types": https://vitest.dev/guide/testing-types [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#references]
- microsoft/TypeScript #26532 - export as namespace (UMD): https://github.com/microsoft/TypeScript/issues/26532 [archived source: https://llms-explorer.com/sources/mdb-context-hub/typescript-declaration-files/#references]
Where this helps
- Publishing a TypeScript library and needing to decide between emitted .d.ts, hand-authored ambient declarations, or both. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Fixing a "Could not find a declaration file for module 'x'" error for an untyped JavaScript dependency. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Shipping a dual ESM+CJS package and needing separate .d.mts/.d.cts files instead of one index.d.ts that "masquerades" as the wrong format. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Extending a third-party library's types (module augmentation) without forking the package, such as adding a custom property to an Express Request object. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
Project ideas
- Author a .d.ts stub (declare module "some-untyped-lib") to unblock a build against a dependency with no @types package, then contribute a real DefinitelyTyped package once the shape stabilizes. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Turn on isolatedDeclarations: true on a library's public API and fix the resulting TS900x errors, unlocking parallel, per-file .d.ts emission by non-tsc tools. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Build a dual-package (ESM + CJS) library with correctly split .d.mts/.d.cts files and a types condition placed first in the exports map, then validate it with @arethetypeswrong/cli. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Write a module-augmentation file that extends a third-party package's types (like adding a custom field to an Express Request) using declare module rather than forking the source. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
Common mistakes
- Placing the types condition after import/require/default in a package's exports map - TypeScript stops at the first matching condition and never reaches types placed later. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Shipping a single index.d.ts for a package that's both ESM and CJS ("type": "module" with a CJS fallback) - arethetypeswrong flags this as "Masquerading as CJS/ESM." [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Hand-maintaining a .d.ts that duplicates a .ts source you already own, instead of emitting it with declaration: true - the hand-written version drifts from the real source over time. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Using /// <reference path="..." /> in published types instead of /// <reference types="..." /> - the path form bakes in a specific file layout that breaks for consumers. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
Known issues
- A .d.ts with any top-level import or export is a module with scoped declarations; one with neither is a script with global declarations - this rule is easy to violate accidentally and silently breaks global-type visibility. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- declare module "x" module augmentation can only merge into an existing shape - it cannot add brand-new top-level exports to the package being augmented. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- Exporting an un-annotated function from a library's entry point can trigger TS2742/TS4082 ("cannot be named") on emit, which only surfaces once the package is actually published and consumed. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
- isolatedDeclarations requires declaration: true (or composite: true) to even be enabled, and its error messages (the TS900x family) can be non-obvious the first time a team adopts it. [source] — AI-suggested, synthesized from this pack's existing facts — not extracted from a source document.
Children
- No children recorded.