Declaration Files in TypeScript

Declaration files (.d.ts) describe the shape of JavaScript code so TypeScript can type-check it. Learn what they are, how they work, and when to use them.

6 min read

A declaration file in TypeScript is a file with a .d.ts extension that describes the types of JavaScript code. It contains no implementation, only type information. When TypeScript compiles your project, declaration files are erased entirely and produce no JavaScript output.

Think of a declaration file as a contract. It tells the TypeScript compiler what a value looks like, what arguments a function accepts, and what properties an object has.

The compiler trusts that contract and uses it to check your code. At runtime, the contract disappears and the original JavaScript runs as usual.

Why Declaration Files Exist

TypeScript is designed to work with JavaScript, including JavaScript libraries that were never written in TypeScript. When you install a popular package, the code on disk is plain JavaScript. Without types, TypeScript cannot help you autocomplete method names, catch wrong argument types, or warn about missing properties.

Declaration files solve this. They sit alongside the JavaScript and provide the type information TypeScript needs, without requiring the library author to rewrite anything in TypeScript.

Here is a concrete example. A plain JavaScript module exports a function that greets a user by name. TypeScript never sees the function body, only the file on disk.

javascriptjavascript
// greeter.js (plain JavaScript)
function greet(name) {
  return `Hello, ${name}!`;
}
module.exports = { greet };

Without a declaration file, TypeScript cannot tell what type the parameter should be or what the function returns. It lets any argument through, including one that is clearly wrong.

typescripttypescript
import { greet } from "./greeter.js";
//     ^ No type information available
greet(42); // No error -- TypeScript does not know this is wrong

Adding a small declaration file next to the JavaScript module fixes this. The file below states that the function takes a string and returns a string, without providing any implementation.

typescripttypescript
// greeter.d.ts
export function greet(name: string): string;

Once TypeScript can read that contract, it type-checks every call to the function and flags mismatched arguments during compilation. Calling the function with a string still succeeds, but a number now produces a compiler error.

typescripttypescript
import { greet } from "./greeter.js";
greet("World"); // OK
greet(42);
//   ~~
//   Argument of type 'number' is not assignable to parameter of type 'string'.

The declaration file added this safety net without touching the original JavaScript, and the error above only exists at compile time. Nothing about it changes how greeter.js runs.

What Goes in a Declaration File

Declaration files use the declare keyword to tell TypeScript about things that exist at runtime. The most common patterns are:

DeclarationWhat it describes
declare varA global variable
declare functionA global function
declare classA class constructor
declare namespaceA namespace of grouped types and values
declare moduleThe types for an imported package
export declare functionA named module export

Here are several patterns in a single declaration file:

typescripttypescript
// my-lib.d.ts
export declare function calculateTotal(items: number[], tax: number): number;
 
export declare const VERSION: string;
 
export declare class Cart {
  constructor(userId: string);
  items: number[];
  addItem(id: number): void;
}

This file describes a module with a function, a constant, and a class, all available to import in TypeScript. The file itself produces zero JavaScript. The actual implementation lives in the matching JavaScript file.

Where Declaration Files Live

TypeScript looks for declaration files in several places automatically:

  1. Bundled with a package: If an npm package includes a types or typings field in its package.json, TypeScript reads that file.

  2. In node_modules/@types: When a package does not ship its own types, the community often publishes them to the DefinitelyTyped repository, installable as a matching @types package.

  3. In your own project: You can create .d.ts files anywhere in your source tree. TypeScript includes them if they are in the compilation scope defined by your tsconfig.json.

For packages that ship without any types at all, you can type them yourself. See type untyped packages in TypeScript.

Declaration Files vs Normal TypeScript Files

A normal TypeScript source file compiles to JavaScript. A declaration file compiles to nothing. The table below shows the practical difference between a source file and its matching declaration file.

FileContainsCompiles to
math.tsImplementation, including the function bodymath.js
math.d.tsType signature only, no function bodyNothing

Because declaration files produce no runtime code, they are safe to publish, share, and install. They describe the contract without adding any weight to the final bundle.

The Relationship Between .d.ts and .js

When you write a TypeScript library and compile it, the compiler can automatically produce declaration files alongside the JavaScript output. Enable this in your tsconfig.json:

jsonjson
{
  "compilerOptions": {
    "declaration": true,
    "declarationDir": "./dist/types"
  }
}

With declaration: true, each source file produces both a JavaScript file with the runtime code and a matching declaration file with the type information. Consumers of your library get autocomplete and type checking without ever seeing your source code.

How Declaration Merging Works with .d.ts

One unique TypeScript feature is that multiple declarations of the same name are merged together. This is useful in declaration files when you want to layer types from different sources.

For a deep dive into this mechanism, see declaration merging in TypeScript.

A practical example is when a library's main export is a function, but you also want to expose a nested type on it:

typescripttypescript
// my-lib.d.ts
declare function myLib(config: { baseUrl: string }): void;
 
declare namespace myLib {
  interface Config {
    baseUrl: string;
    timeout?: number;
  }
}
 
export = myLib;

The function declaration and the namespace declaration both share the same name and merge into one export. Consumers can call the function directly and also reference the nested Config type on it.

When to Write a Declaration File

Write a declaration file when:

  • You are consuming a JavaScript library that lacks types and you want type safety.
  • You are publishing a TypeScript library and want consumers to get autocomplete.
  • You want to add types for global variables introduced by a script tag or runtime environment.
  • You need to augment an existing module with additional type information.

For published libraries, letting the compiler generate declaration files from your source with declaration: true is the standard approach. Hand-written declaration files are mainly for describing external JavaScript that you do not control.

Rune AI

Rune AI

Key Insights

  • A .d.ts file contains only type information and produces no runtime JavaScript.
  • Use declare to tell TypeScript about values that exist at runtime but were not written in TypeScript.
  • Declaration files make it possible to use JavaScript libraries with full type safety.
  • TypeScript automatically discovers .d.ts files under node_modules/@types.
  • You can write your own .d.ts file to type a package that ships without types.
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between a .ts file and a .d.ts file?

A .ts file contains implementation code that compiles to JavaScript. A .d.ts file contains only type information and produces no JavaScript output. It describes types without adding runtime code.

Do declaration files affect runtime behavior?

No. Declaration files are erased during compilation. They only exist to tell the TypeScript compiler about the types of values that already exist at runtime.

Where should I put declaration files in my project?

For your own code, put them next to the implementation files. For external packages, they belong in an @types folder or in the package's own distribution. TypeScript also reads declaration files from node_modules/@types automatically.

Conclusion

Declaration files are the bridge between TypeScript and JavaScript. They describe the shape of code that already exists at runtime without adding any new runtime behavior themselves. When you import a JavaScript library, TypeScript looks for a matching .d.ts file to understand what types to expect.