Writing Declaration Files

· 4 min read · Updated March 17, 2026 · advanced
typescript type-checking declarations d.ts

TypeScript declaration files (.d.ts) let you add type information to plain JavaScript code. When you’re working with a JavaScript library that has no types, or when you want to gradually adopt TypeScript in an existing project, declaration files become essential. They describe the shape of your code without changing how it runs.

What Is a Declaration File?

A declaration file contains only type information. It tells TypeScript what types exist in your JavaScript code without adding any runtime behavior. The file ends with .d.ts and gets automatically picked up by TypeScript.

Here’s the simplest declaration file:

// greeter.js
function greet(name) {
  return "Hello, " + name + "!";
}
// greeter.d.ts
declare function greet(name: string): string;

The declare keyword tells TypeScript that something exists but was defined elsewhere. You can declare functions, variables, classes, and modules.

Creating Declaration Files for a JavaScript Project

When you have a JavaScript project and want TypeScript to understand it, you have three main approaches.

Approach 1: Automatic Declaration Generation

If you’re converting a JavaScript project to TypeScript incrementally, set declare in your tsconfig.json:

{
  "compilerOptions": {
    "allowJs": true,
    "declaration": true,
    "outDir": "./dist"
  }
}

With these options, TypeScript automatically generates .d.ts files for your JavaScript code during compilation.

Approach 2: Ambient Declaration Files

Create a types folder and add declaration files that TypeScript will automatically find:

// types/my-library.d.ts
declare module "my-library" {
  export function calculate(a: number, b: number): number;
  export const VERSION: string;
}

TypeScript looks for .d.ts files in your typeRoots or automatically in a types directory.

Approach 3: Publishing Declaration Files

If you’re publishing a library to npm, include the declaration files in your package:

{
  "name": "my-library",
  "types": "dist/index.d.ts"
}

When you build your TypeScript project with declaration: true, TypeScript outputs the .d.ts files to your out directory.

Extending Existing Types

Sometimes you need to add types to something that already has them. TypeScript lets you extend built-in types and third-party library types.

Module Augmentation

You can add to existing module exports:

// Extend the Express Request type
import 'express';

declare module 'express' {
  export interface Request {
    user?: {
      id: string;
      role: string;
    };
  }
}

Now every Request object in your Express app has an optional user property:

import { Request, Response, NextFunction } from 'express';

function authenticate(req: Request, res: Response, next: NextFunction) {
  if (req.user) {
    console.log(`User ${req.user.id} authenticated`);
  }
  next();
}

Global Augmentation

You can also extend global types that aren’t part of a module:

declare global {
  interface String {
    capitalize(): string;
  }
}

String.prototype.capitalize = function() {
  return this.charAt(0).toUpperCase() + this.slice(1);
};

This adds a capitalize() method to all strings in your codebase.

Practical Patterns

Declaration Files for Functions

When declaring function types, be specific about parameters and return types:

// Good: specific types
declare function fetchData(url: string, options?: RequestInit): Promise<any>;

// Good: overloads for different calling patterns
declare function transform(input: string): string;
declare function transform(input: number): number;
declare function transform(input: string | number): string | number;

Declaration Files for Classes

Match the JavaScript runtime behavior in your declarations:

// Point.js (runtime code)
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  
  distanceTo(other) {
    return Math.sqrt(
      Math.pow(this.x - other.x, 2) + 
      Math.pow(this.y - other.y, 2)
    );
  }
}
// Point.d.ts
declare class Point {
  constructor(x: number, y: number);
  x: number;
  y: number;
  distanceTo(other: Point): number;
}

Declaration Files for Objects

For object literals and namespaces:

declare const config: {
  apiUrl: string;
  timeout: number;
  retries: number;
};

declare namespace MyUtils {
  function parse(input: string): object;
  const VERSION: string;
}

Declaration Files for npm Packages

When creating types for an npm package you’re publishing, include the declaration file in your published package:

{
  "name": "my-utils",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "files": ["dist"]
}

Build your package with TypeScript to generate both the JavaScript and declaration files:

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

When consumers run npm install my-utils, TypeScript automatically finds and uses the declaration file.

Common Pitfalls

Forgetting to Export

In module declaration files, everything must be explicitly exported:

// Wrong - TypeScript won't see these
declare module "my-module" {
  function foo(): void; // Won't work
}

// Correct
declare module "my-module" {
  export function foo(): void;
}

Mismatch Between JS and .d.ts

Your declaration file must match your JavaScript exactly:

// runtime.js
function greet(name = "World") {
  return `Hello, ${name}!`;
}
// runtime.d.ts - WRONG
declare function greet(name: string): string;

// runtime.d.ts - CORRECT  
declare function greet(name?: string): string;

The parameter is optional in the JS code, so it must be optional in the declaration.

Triple-Slash Directives

Older code often uses triple-slash directives to reference declaration files:

/// <reference path="./types.d.ts" />

Modern projects prefer tsconfig.json paths or typeRoots instead.

See Also