TypeScript Declaration Files — Complete Guide
In this tutorial, you will learn about TypeScript Declaration Files. We cover key concepts, practical examples, and best practices to help you master this topic.
TypeScript declaration files (.d.ts) describe the shape of JavaScript modules and global variables without providing implementation — they are the bridge that brings type safety to the entire JavaScript ecosystem.
What You'll Learn
- What
.d.tsfiles are and how they work - The
declarekeyword for global and module types - Ambient module declarations
- DefinitelyTyped and
@types/packages - Writing custom declaration files
Why It Matters
TypeScript cannot type-check JavaScript modules without type information. Declaration files provide that information without modifying the original JS code. Every @types/ package on npm is a collection of .d.ts files, making thousands of JS libraries usable in TypeScript.
Real-World Use
The Doda Browser extension API types live in @types/chrome — a DefinitelyTyped package containing hundreds of .d.ts files describing every Chrome API. Without them, extension development in TypeScript would be impossible. Durga Antivirus Pro uses custom .d.ts files to type REST API endpoints.
Learning Path
flowchart LR A[Namespaces & Modules] --> B[Declaration Files] B --> C[Type Manipulation] B --> D[You Are Here] C --> E[Classes & OOP] E --> F[Inheritance]
What Is a .d.ts File?
A .d.ts file contains only type information — no runtime code:
// my-lib.d.ts
export function greet(name: string): string;
export const VERSION: string;
export interface Config {
debug: boolean;
timeout: number;
}
When TypeScript compiles, it uses .d.ts files for Type Checking without generating any JavaScript from them.
Generating .d.ts from .ts
Enable in tsconfig.json:
{
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": false
}
}
Now compiling math.ts:
export function add(a: number, b: number): number {
return a + b;
}
Generates math.d.ts:
export declare function add(a: number, b: number): number;
The declare Keyword
Use declare to tell TypeScript that a value exists at runtime without providing an implementation:
// globals.d.ts
declare const API_BASE_URL: string;
declare function sendRequest(path: string): Promise<unknown>;
declare class ApiClient {
constructor(baseUrl: string);
get<T>(path: string): Promise<T>;
}
declare var, let, const
declare var environment: string; // Can be reassigned
declare let debugMode: boolean; // Block-scoped
declare const APP_VERSION: string; // Constant — cannot be reassigned
declare function
declare function $(selector: string): HTMLElement[];
declare function $(element: HTMLElement): HTMLElement[];
// Overloads
declare function format(input: string): string;
declare function format(input: number): string;
declare class
declare class Animal {
constructor(name: string);
speak(): void;
readonly name: string;
}
declare namespace
declare namespace MyLib {
function init(config: Record<string, unknown>): void;
const VERSION: string;
namespace Events {
function on(event: string, handler: () => void): void;
}
}
declare module
// For ambient modules (modules without type definitions)
declare module "some-untyped-lib" {
export function doStuff(): void;
export const version: string;
}
// For non-JS file imports
declare module "*.svg" {
const content: string;
export default content;
}
declare module "*.module.css" {
const classes: Record<string, string>;
export default classes;
}
declare global
Used inside a module to add declarations to the global scope:
// src/types/global.d.ts
export {};
declare global {
interface Window {
__APP_CONFIG__: {
API_URL: string;
VERSION: string;
};
}
const __DEV__: boolean;
}
// Now usable anywhere without import
console.log(window.__APP_CONFIG__.API_URL);
Ambient Module Declarations
For untyped npm packages:
// src/types/untyped.d.ts
declare module "obscure-js-library" {
export interface Options {
timeout?: number;
retries?: number;
}
export function run(options?: Options): Promise<string>;
export const DEFAULT_TIMEOUT: number;
}
Wildcard Module Declarations
// Match all files of a certain pattern
declare module "*.json" {
const value: unknown;
export default value;
}
declare module "*.md" {
const content: string;
export default content;
}
DefinitelyTyped
DefinitelyTyped is a GitHub Repository containing thousands of community-maintained .d.ts files for popular JavaScript libraries.
Using @types/ Packages
npm install --save-dev @types/react
npm install --save-dev @types/express
npm install --save-dev @types/node
TypeScript automatically finds type definitions in node_modules/@types/ unless you set typeRoots in tsconfig.
Finding Type Definitions
- Check the library's documentation for built-in types
- Search for
@types/library-nameon npm - Check DefinitelyTyped on GitHub:
https://github.com/DefinitelyTyped/DefinitelyTyped - If none exists, write your own ambient declaration
Writing Custom Declaration Files
When a Library Has No Types
// types/d3-heatmap.d.ts
declare module "d3-heatmap" {
export interface HeatmapConfig {
data: number[][];
width?: number;
height?: number;
colors?: string[];
}
export function createHeatmap(container: HTMLElement, config: HeatmapConfig): void;
export function updateHeatmap(config: HeatmapConfig): void;
}
Merging Existing Types
// Extend an existing module's types
import "express";
declare module "express" {
interface Request {
user?: {
id: string;
role: "admin" | "user";
};
requestId?: string;
}
}
Publishing Type Definitions
If you publish a TypeScript library, ensure consumers get type information:
{
"name": "my-typescript-lib",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist"]
}
The types field points to the entry .d.ts file. TypeScript automatically picks it up when consumers import your package.
Common Mistakes
1. Using declare module for a Package That Already Has Types
Check if the package already ships its own .d.ts files (check the types field in its package.json). Only use declare module as a fallback.
2. Declaration Files Not Being Included
Ensure your tsconfig includes the declaration files:
{
"include": ["src/**/*", "types/**/*.d.ts"]
}
3. Writing Runtime Code in .d.ts Files
// BAD: .d.ts should only have types
export function add(a: number, b: number): number {
return a + b; // Runtime code — does not belong in .d.ts
}
// GOOD: just the signature
export declare function add(a: number, b: number): number;
4. Not Using declare Inside .d.ts
Inside a .d.ts file, everything is implicitly declare. But using it explicitly is good practice.
5. Confusing .d.ts and .ts Files
.tsfiles: implementation + types, generates.js+.d.ts.d.tsfiles: types only, no JavaScript generated
6. Missing export {} for Global Augmentations
When using declare global inside a module, you must include export {} to mark the file as a module:
// Without this, the file is treated as a script, not a module
export {};
declare global {
// ...
}
Practice Questions
What is the purpose of a
.d.tsfile? To describe the types of JavaScript code without providing runtime implementation, enabling type checking for JS libraries.What does the
declarekeyword do? It tells TypeScript that a value exists at runtime, providing its type without requiring an implementation.What is DefinitelyTyped? A community-maintained repository of
.d.tsfiles for thousands of JavaScript libraries, distributed as@types/npm packages.How do you extend an existing module's types? Use module augmentation:
declare module "existing-module" { ... }to add properties to its interfaces.
Challenge: Write a .d.ts file for a hypothetical csv-parser library that exports a parseCSV(text: string): Record<string, string>[] function and a CsvParserOptions interface. Then write a separate script that uses it.
FAQ
Mini Project: Type Definitions for a Weather API
// types/weather-api.d.ts
declare module "weather-api" {
export interface Coordinates {
lat: number;
lon: number;
}
export interface WeatherData {
temperature: number;
humidity: number;
condition: "sunny" | "cloudy" | "rainy" | "snowy";
windSpeed: number;
timestamp: string;
}
export interface ForecastDay {
date: string;
high: number;
low: number;
condition: string;
}
export function getCurrentWeather(coords: Coordinates): Promise<WeatherData>;
export function getForecast(coords: Coordinates, days: number): Promise<ForecastDay[]>;
export function setApiKey(key: string): void;
export const DEFAULT_UNITS: "metric" | "imperial";
}
// Use it:
import { getCurrentWeather, getForecast } from "weather-api";
async function displayWeather(lat: number, lon: number) {
const current = await getCurrentWeather({ lat, lon });
console.log(`Temperature: ${current.temperature}°C, ${current.condition}`);
const forecast = await getForecast({ lat, lon }, 5);
for (const day of forecast) {
console.log(`${day.date}: ${day.high}°C / ${day.low}°C — ${day.condition}`);
}
}
What's Next
Now explore the satisfies operator and branded types:
| Lesson | Description |
|---|---|
| {{< ref "/programming-languages/typescript/16-namespaces-modules" >}} | Review namespaces and modules |
| {{< ref "/programming-languages/typescript/18-type-manipulation" >}} | satisfies, branded types |
| {{< ref "/programming-languages/typescript/19-classes" >}} | Classes in TypeScript |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro