Declaration Files & @types
Understand how .d.ts files work, when to write your own, and how DefinitelyTyped and @types packages bring type safety to the JavaScript ecosystem.
Overview
Key Points
- •Declaration files end in `.d.ts` and contain only type information - no runtime code
- •`declare module "name"` defines the shape of an external module
- •`declare global` extends the global scope with new types
- •`@types/` packages (from DefinitelyTyped) are installed with `npm install --save-dev @types/library-name`
- •TypeScript finds `@types/` packages automatically; no import needed for global types
- •`"types"` field in package.json tells TypeScript where a library's declaration file lives
- •`"skipLibCheck": true` in tsconfig skips type checking `.d.ts` files (speeds up builds)
- •Ambient declarations: `declare const`, `declare function`, `declare class` - describe globals
Examples
Writing a basic declaration file for a JS library
// Say you have a JS file: utils.js
// export function formatDate(date, format) { ... }
// export function clamp(value, min, max) { ... }
// You create utils.d.ts to describe its types:
declare module './utils' {
export function formatDate(date: Date, format: string): string;
export function clamp(value: number, min: number, max: number): number;
}
// Or if it's a global module (no import needed):
declare function formatDate(date: Date, format: string): string;
declare const APP_VERSION: string;
// Now TypeScript understands the types:
import { formatDate, clamp } from './utils';
const formatted = formatDate(new Date(), 'YYYY-MM-DD'); // string
const clamped = clamp(150, 0, 100); // numberUsing @types packages
// Install: npm install --save-dev @types/lodash
// TypeScript now knows lodash's types
import _ from 'lodash';
// Full type safety - IntelliSense knows all lodash methods
const users = [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
{ name: 'Carol', age: 35 },
];
const sorted = _.sortBy(users, 'age');
// sorted: { name: string; age: number }[]
const byName = _.keyBy(users, 'name');
// byName: { [key: string]: { name: string; age: number } }
const chunked = _.chunk([1, 2, 3, 4, 5], 2);
// chunked: number[][]
// Without @types/lodash, all of these would be 'any'Ambient module declarations and global augmentation
// Declare a module for file types TypeScript doesn't understand
// (e.g., in a bundler that handles .svg imports)
// src/global.d.ts
declare module '*.svg' {
const content: string;
export default content;
}
declare module '*.png' {
const content: string;
export default content;
}
// Extend globalThis with your own globals
declare global {
interface Window {
analytics: {
track: (event: string, data?: Record<string, unknown>) => void;
};
}
const __DEV__: boolean;
const __VERSION__: string;
}
// Now these work without errors:
window.analytics.track('page_view', { path: '/home' });
if (__DEV__) {
console.log('Development mode:', __VERSION__);
}Common Mistakes
- Installing `@types/package` for a library that already ships its own types (like `axios` or `zod`) - this causes type conflicts; check if the package has a `"types"` field in its package.json first
- Writing `.d.ts` files with implementation code - declaration files must only contain `declare` statements; any actual code goes in `.ts` files
- Forgetting that `declare global` must be inside a module (a file with at least one import/export) - otherwise TypeScript treats the whole file as a global script and `declare global` is redundant
Note
Before writing a `.d.ts` file for a popular library, check `https://www.npmjs.com/package/@types/library-name` or look for a `"types"` field in the library's `package.json`. Most popular packages already have types. Only write your own declarations for internal JS files, niche libraries, or file-type imports (like `.svg`, `.css`).
Exercises
Write TypeScript code to solve each exercise. Click Run to compile and execute it right here, then mark as done.
Completed: 0 / 3 exercises
Knowledge Check
Question 1 of 4