Technology · TypeScript

TypeScript Declaration Files

Write .d.ts files, publish typed npm packages, and create type stubs.

TL;DR
  1. 01Create .d.ts files to provide types for JavaScript libraries.
  2. 02Use declare keyword to define types without implementation.
  3. 03Publish types alongside your npm package for TypeScript users.

Writing Declaration Files

  • Create .d.ts files to define types for JavaScript code.
    // mylib.d.ts
    export function greet(name: string): string;
    export interface User {
      id: number;
      name: string;
    }
  • Declaration files contain only type definitions, no implementation.
    // index.d.ts
    export class Calculator {
      add(a: number, b: number): number;
      subtract(a: number, b: number): number;
    }
  • Use declare to define types for existing JavaScript globals.
    declare global {
      interface Window {
        myGlobal: string;
      }
    }
  • Declare ambient variables that exist at runtime but not in source.
    declare const __VERSION__: string;
    declare function require(module: string): any;
  • Use declare module for UMD or non-ES library entry points.
    export as namespace MyLib;
    export function init(options: Options): void;
    export interface Options {
      debug?: boolean;
    }

Module Declaration

  • Declare types for entire modules or namespaces.
    declare module "my-library" {
      export function process(data: any): any;
      export const version: string;
    }
  • Augment existing modules with additional types.
    declare module "express" {
      interface Request {
        userId?: number;
      }
    }
  • Use triple-slash directives to reference other declaration files.
    /// <reference path="./types.d.ts" />
    /// <reference types="node" />
  • Organize types with namespaces for large libraries.
    declare namespace MyLib {
      interface Config { }
      function init(config: Config): void;
    }
  • Use a wildcard module declaration for file imports like CSS or SVG.
    declare module "*.svg" {
      const content: string;
      export default content;
    }
    
    declare module "*.css" {
      const styles: Record<string, string>;
      export default styles;
    }

Package Configuration

  • Point to declaration files in package.json with the types field.
    {
      "name": "my-library",
      "version": "1.0.0",
      "main": "dist/index.js",
      "types": "dist/index.d.ts"
    }
  • Emit declaration files from TypeScript during build.
    {
      "compilerOptions": {
        "declaration": true,
        "declarationDir": "./dist",
        "outDir": "./dist"
      }
    }
  • Use typesVersions to support different TypeScript versions.
    {
      "typesVersions": {
        "<=4.0": { "*": ["ts4.0/*"] },
        ">=4.1": { "*": ["*"] }
      }
    }
  • Use exports map with types conditions for dual CJS/ESM packages.
    {
      "exports": {
        ".": {
          "import": { "types": "./dist/esm/index.d.ts", "default": "./dist/esm/index.js" },
          "require": { "types": "./dist/cjs/index.d.ts", "default": "./dist/cjs/index.js" }
        }
      }
    }
  • Generate declaration maps for go-to-definition in source.
    {
      "compilerOptions": {
        "declarationMap": true,
        "declaration": true,
        "sourceRoot": "./src"
      }
    }

Publishing to DefinitelyTyped

  • Contribute type definitions for popular untyped libraries.
    DefinitelyTyped/types/
      library-name/
        index.d.ts
        package.json
        tsconfig.json
        tests.ts
  • Test declaration files with sample code before submitting.
    // tests.ts
    import * as lib from "./index";
    
    const result: string = lib.process("test");
    const version: string = lib.version;
  • Include a package.json in the types folder with the correct shape.
    {
      "name": "@types/library-name",
      "version": "1.0.0",
      "typings": "index.d.ts"
    }
  • Add a tsconfig.json to validate declarations during CI.
    {
      "compilerOptions": {
        "module": "commonjs",
        "lib": ["es6"],
        "noImplicitAny": true,
        "noImplicitThis": true,
        "strictNullChecks": true,
        "strictFunctionTypes": true
      }
    }
  • Run dtslint to catch type errors before opening a pull request.
    npm install -g dtslint
    dtslint types/library-name

Best Practices

  • Keep declaration files in sync with implementation.
    // Don't declare types that don't match the implementation
    export function add(a: number, b: number): number;
    // Should match: const add = (a, b) => a + b;
  • Export only public APIs and hide internal helpers.
    // Correct: public types are exported
    export interface PublicAPI { }
    
    // Wrong: internal types shouldn't be exported
    // export interface _InternalHelper { }
  • Include JSDoc comments for better IDE support.
    /**
     * Processes data asynchronously
     * @param data - The input data to process
     * @returns A promise that resolves with the result
     */
    export function processAsync(data: any): Promise<any>;
  • Use semver versioning for types to avoid breaking changes.
    # Bump patch for additions, minor for new overloads, major for removals
    npm version patch  # safe: new optional param
    npm version major  # breaking: removed export
  • Avoid using any in declaration files — prefer unknown or generics.
    // Weak: loses type information
    export function parse(input: string): any;
    
    // Better: let the caller specify the return type
    export function parse<T>(input: string): T;

Tip: Always include declaration files with your npm package or publish to DefinitelyTyped — TypeScript users will appreciate the type safety.

Warning: Keep declaration files accurate and in sync with implementation — incorrect types are worse than no types at all.

TypeScript Async PatternsTypeScript Decorators