Topics

Jump to a topic.

Search questions

Find a question by title, tag, or summary.

What is a .d.ts file, and how do you write one for an untyped library?

Questions in this chapter (6)
  • Draft
  • medium
  • Askedvery often
  • Verified October 2026

Answer

A declaration file (.d.ts) describes the shape of JavaScript that lives in another file. It contains types and declared values only, so it is an ambient context: no function bodies (TS1183) or variable initializers (TS1039), and tsc emits nothing for it.

Where types for a package come from, in the order you reach for them:

  1. Bundled with the package. The package.json types field (or typings), or a types condition inside exports. Authors generate them with declaration: true, plus emitDeclarationOnly for a JavaScript project.
  2. DefinitelyTyped. Community-written @types/foo for the npm package foo, installed as a dev dependency. tsc finds them on import even when types is limited.
  3. Your own. Needed when neither exists. Without it, strict code importing the package fails with TS7016.

Writing one for an untyped library.

  • Create a .d.ts file your tsconfig includes, with no top-level import or export, so it stays a global script.
  • Start with the shorthand declare module "name";, which makes every import any. Then replace it with declare module "name" { export function ... } for the parts you use.
  • Match the module's real shape: export = for a module.exports = fn package (see the interop question).
  • Keep it minimal and type only what you call; contribute it to DefinitelyTyped when it is stable.

Version note. TS 6.0 changed types to default to []: global-declaring @types packages such as @types/node are no longer included automatically, so add "types": ["node"].

Example

// src/untyped-lib.d.ts  (TypeScript 7.0.2 and 5.9.3, strict)
declare module "untyped-lib" {
  export function greet(name: string): string;
}
// Without this file, `import { greet } from "untyped-lib"` reports:
// Error TS7016: Could not find a declaration file for module 'untyped-lib'. '.../node_modules/untyped-lib/index.js' implicitly has an 'any' type.
// The shorthand `declare module "untyped-lib";` compiles with no error and types every import as any.

// src/index.ts, compiled with --declaration --emitDeclarationOnly (identical output on both versions)
export function greet(name: string) { return `hello ${name}`; }
export class Counter { #n = 0; inc() { return ++this.#n; } }
// index.d.ts:
//   export declare function greet(name: string): string;
//   export declare class Counter { #private; inc(): number; }
// Output is shown condensed; tsc prints the class body on several lines.

// A .d.ts cannot contain code:
// export function f() { return 1; }
// Error TS1183: An implementation cannot be declared in ambient contexts.

Pitfalls

  • Adding a top-level import or export to the file that holds declare module "x" { ... }. It then becomes a module and the block is treated as an augmentation: both versions reported TS2665 ("Invalid module name in augmentation. Module 'untyped-lib' resolves to an untyped module") and the original TS7016 stayed.
  • Believing @types/foo needs a types entry. On 7.0.2 with types unset, import ms from "ms" found @types/ms, while the global process from @types/node failed with TS2591 until --types node was passed.
  • Using declare module "x"; as the finished answer. It silences TS7016, but greet(1, 2, 3) compiled without any error, so you lose all checking.
  • Shipping .d.ts files that import @types/* while listing those packages only in devDependencies. The publishing guide says to put them in dependencies.

Follow-ups

Package has both types and exports. Which does tsc use?
Under nodenext and bundler, exports wins, with types as the first condition. The top-level types field still serves resolvers that ignore exports; in my node10 test tsc used it (see the module-resolution question).
Why not just write .d.ts by hand for my own code?
Hand-written files drift from the implementation. Let declaration: true generate them, or use JSDoc types in JavaScript.