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.
// 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.
// 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.
// 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.
DefinitelyTyped, @types packages, custom declarations, type acquisition, typeRoots