DefinitelyTyped and @types Packages

Difficulty: Advanced

DefinitelyTyped is the largest repository of TypeScript type definitions in the world, hosted on GitHub as a community-driven project. It provides .d.ts declaration files for thousands of JavaScript libraries that were not originally written in TypeScript. These type definitions are published to npm under the @types scope - for example, type definitions for the 'lodash' library are available as '@types/lodash'. When you install an @types package, TypeScript automatically picks up the declarations.

TypeScript resolves @types packages through the `typeRoots` configuration. By default, TypeScript looks in `node_modules/@types` for type definitions. You can customize this by setting `typeRoots` in your tsconfig.json to point to additional directories. The `types` option lets you whitelist which @types packages are included - if specified, only the listed packages are loaded, which can speed up compilation in large projects.

Sometimes you need to write custom declarations for libraries that have no @types package, for internal packages, or for augmenting existing types. You can create a .d.ts file in your project (commonly in a `types/` or `@types/` directory) and declare the module shape. For global augmentations - like adding a property to the Window interface - you use `declare global` inside a module file. This is a powerful technique for extending existing type definitions.

When deciding between bundled types and @types packages, know that many modern libraries ship their own .d.ts files alongside their JavaScript. TypeScript checks for a `types` or `typings` field in the library's package.json first. If that exists, no @types package is needed. Libraries like Axios, Zod, and date-fns include built-in TypeScript support. You only need @types when the library itself does not include type declarations.

Code examples

Using @types Packages

// Install types for a JS library:
// npm install lodash
// npm install -D @types/lodash

// TypeScript now knows the types:
// import _ from 'lodash';
// _.chunk([1, 2, 3, 4], 2);  // TypeScript knows this returns number[][]

// Simulating what @types provides
interface LodashStatic {
  chunk<T>(array: T[], size: number): T[][];
  uniq<T>(array: T[]): T[];
  flatten<T>(array: T[][]): T[];
}

const _: LodashStatic = {
  chunk<T>(array: T[], size: number): T[][] {
    const result: T[][] = [];
    for (let i = 0; i < array.length; i += size) {
      result.push(array.slice(i, i + size));
    }
    return result;
  },
  uniq<T>(array: T[]): T[] {
    return [...new Set(array)];
  },
  flatten<T>(array: T[][]): T[] {
    return array.reduce((acc, val) => acc.concat(val), []);
  }
};

console.log(JSON.stringify(_.chunk([1, 2, 3, 4, 5, 6], 2)));
console.log(JSON.stringify(_.uniq([1, 2, 2, 3, 3, 3])));
console.log(JSON.stringify(_.flatten([[1, 2], [3, 4], [5]])));

The @types/lodash package provides TypeScript with type information for the lodash library. This gives you autocomplete, type checking, and documentation without lodash itself being written in TypeScript.

Writing Custom Type Declarations

// custom-lib.d.ts - declaring types for an untyped library
// declare module 'my-analytics' {
//   export function init(apiKey: string): void;
//   export function track(event: string, data?: object): void;
//   export interface Config {
//     debug: boolean;
//     endpoint: string;
//   }
//   export function configure(config: Partial<Config>): void;
// }

// Simulating the custom declaration
interface Config {
  debug: boolean;
  endpoint: string;
}

const myAnalytics = {
  init(apiKey: string): void {
    console.log(`Initialized with key: ${apiKey.slice(0, 4)}...`);
  },
  track(event: string, data?: object): void {
    console.log(`Tracked: ${event}`);
  },
  configure(config: Partial<Config>): void {
    console.log(`Configured: debug=${config.debug}`);
  }
};

myAnalytics.init('sk_live_abc123');
myAnalytics.track('page_view');
myAnalytics.configure({ debug: true });

When no @types package exists, you write your own .d.ts file using 'declare module' to describe the library's API shape.

Global Augmentation

// Augmenting the global Window interface (in a .d.ts file):
// declare global {
//   interface Window {
//     analytics: { track: (event: string) => void };
//   }
// }

// Simulating global augmentation
interface CustomGlobal {
  appName: string;
  version: string;
  features: string[];
}

const globals: CustomGlobal = {
  appName: 'MyApp',
  version: '2.1.0',
  features: ['auth', 'dashboard', 'reports']
};

console.log(`${globals.appName} v${globals.version}`);
console.log(`Features: ${globals.features.join(', ')}`);
console.log(`Feature count: ${globals.features.length}`);

Global augmentation lets you extend existing global interfaces like Window, NodeJS.ProcessEnv, or any other ambient type.

Key points

Concepts covered

DefinitelyTyped, @types packages, custom declarations, type acquisition, typeRoots