Declaration files (.d.ts) provide type information for JavaScript libraries. Here's how to write them effectively.
Basic Declaration Structure#
1// types/my-library.d.ts
2
3// Declare module for npm package
4declare module 'my-library' {
5 // Export types
6 export interface Config {
7 apiKey: string;
8 timeout?: number;
9 debug?: boolean;
10 }
11
12 // Export functions
13 export function initialize(config: Config): void;
14 export function getData(id: string): Promise<Data>;
15
16 // Export classes
17 export class Client {
18 constructor(config: Config);
19 connect(): Promise<void>;
20 disconnect(): void;
21 send(message: string): Promise<Response>;
22 }
23
24 // Export type aliases
25 export type Status = 'pending' | 'active' | 'completed';
26
27 // Default export
28 const defaultExport: {
29 version: string;
30 Client: typeof Client;
31 initialize: typeof initialize;
32 };
33
34 export default defaultExport;
35}Global Declarations#
1// types/globals.d.ts
2
3// Declare global variable
4declare const CONFIG: {
5 apiUrl: string;
6 environment: string;
7};
8
9// Declare global function
10declare function gtag(
11 command: 'config' | 'event',
12 targetId: string,
13 config?: Record<string, any>
14): void;
15
16// Extend Window interface
17declare global {
18 interface Window {
19 analytics: {
20 track: (event: string, data?: Record<string, any>) => void;
21 identify: (userId: string) => void;
22 };
23 __INITIAL_STATE__: any;
24 }
25}
26
27// Extend existing module
28declare module 'express' {
29 interface Request {
30 user?: {
31 id: string;
32 email: string;
33 role: string;
34 };
35 }
36}
37
38// Ambient declaration (no import needed)
39declare namespace NodeJS {
40 interface ProcessEnv {
41 NODE_ENV: 'development' | 'production' | 'test';
42 DATABASE_URL: string;
43 API_KEY: string;
44 }
45}
46
47export {}; // Make this a moduleModule Augmentation#
1// Extend third-party library types
2
3// Extend React
4import 'react';
5
6declare module 'react' {
7 interface CSSProperties {
8 // Allow CSS custom properties
9 [key: `--${string}`]: string | number;
10 }
11}
12
13// Extend express-session
14import 'express-session';
15
16declare module 'express-session' {
17 interface SessionData {
18 userId: string;
19 cart: CartItem[];
20 preferences: UserPreferences;
21 }
22}
23
24// Extend prisma client
25import { PrismaClient } from '@prisma/client';
26
27declare module '@prisma/client' {
28 interface PrismaClient {
29 $extends: any;
30 }
31}
32
33// Add custom matchers to Jest
34declare global {
35 namespace jest {
36 interface Matchers<R> {
37 toBeWithinRange(floor: number, ceiling: number): R;
38 toHaveBeenCalledWithError(error: Error): R;
39 }
40 }
41}Function Overloads#
1// types/utils.d.ts
2
3// Function with multiple signatures
4declare function format(value: string): string;
5declare function format(value: number, decimals?: number): string;
6declare function format(value: Date, pattern?: string): string;
7
8// Generic function
9declare function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>;
10
11// Callback-style function
12declare function fetchData(
13 url: string,
14 callback: (error: Error | null, data: any) => void
15): void;
16
17// Promise-style function
18declare function fetchData(url: string): Promise<any>;
19
20// Options object pattern
21declare function request(
22 url: string,
23 options?: {
24 method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
25 headers?: Record<string, string>;
26 body?: any;
27 timeout?: number;
28 }
29): Promise<Response>;Class Declarations#
1// types/classes.d.ts
2
3declare class EventEmitter {
4 // Constructor overloads
5 constructor();
6 constructor(options: { maxListeners: number });
7
8 // Methods
9 on(event: string, listener: (...args: any[]) => void): this;
10 once(event: string, listener: (...args: any[]) => void): this;
11 off(event: string, listener: (...args: any[]) => void): this;
12 emit(event: string, ...args: any[]): boolean;
13
14 // Static members
15 static defaultMaxListeners: number;
16
17 // Protected/private
18 protected listeners: Map<string, Function[]>;
19}
20
21// Generic class
22declare class Repository<T extends { id: string }> {
23 constructor(collection: string);
24
25 find(id: string): Promise<T | null>;
26 findAll(filter?: Partial<T>): Promise<T[]>;
27 create(data: Omit<T, 'id'>): Promise<T>;
28 update(id: string, data: Partial<T>): Promise<T>;
29 delete(id: string): Promise<boolean>;
30}
31
32// Abstract class
33declare abstract class BaseService {
34 abstract initialize(): Promise<void>;
35 abstract shutdown(): Promise<void>;
36
37 protected logger: Logger;
38 readonly name: string;
39}Namespace Declarations#
1// types/namespace.d.ts
2
3declare namespace MyLib {
4 // Nested types
5 interface Config {
6 debug: boolean;
7 }
8
9 namespace Utils {
10 function formatDate(date: Date): string;
11 function parseDate(str: string): Date;
12 }
13
14 // Callable namespace
15 function init(config: Config): void;
16
17 // Nested namespace
18 namespace Http {
19 interface RequestOptions {
20 method: string;
21 url: string;
22 }
23
24 function request(options: RequestOptions): Promise<Response>;
25 }
26}
27
28// UMD module pattern
29export = MyLib;
30export as namespace MyLib;
31
32// Usage in browser (UMD)
33// MyLib.init({ debug: true });
34// MyLib.Utils.formatDate(new Date());
35
36// Usage in Node.js
37// import MyLib from 'my-lib';
38// MyLib.init({ debug: true });Conditional Types in Declarations#
1// types/conditional.d.ts
2
3// Conditional return type
4declare function getValue<T extends 'string' | 'number' | 'boolean'>(
5 type: T
6): T extends 'string'
7 ? string
8 : T extends 'number'
9 ? number
10 : boolean;
11
12// Infer from options
13interface Options {
14 async?: boolean;
15}
16
17declare function process<T extends Options>(
18 data: string,
19 options?: T
20): T extends { async: true } ? Promise<Result> : Result;
21
22// Mapped types
23type Getters<T> = {
24 [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
25};
26
27declare function createGetters<T extends object>(obj: T): Getters<T>;
28
29// Template literal types
30type EventName<T extends string> = `on${Capitalize<T>}`;
31
32declare function addHandler<T extends string>(
33 event: T,
34 handler: (event: EventName<T>) => void
35): void;Third-Party Library Example#
1// types/analytics-lib.d.ts
2
3declare module 'analytics-lib' {
4 export interface AnalyticsConfig {
5 writeKey: string;
6 debug?: boolean;
7 plugins?: Plugin[];
8 }
9
10 export interface Plugin {
11 name: string;
12 initialize: (config: AnalyticsConfig) => void;
13 track: (event: TrackEvent) => void;
14 identify: (traits: UserTraits) => void;
15 }
16
17 export interface TrackEvent {
18 event: string;
19 properties?: Record<string, any>;
20 timestamp?: Date;
21 }
22
23 export interface UserTraits {
24 userId: string;
25 email?: string;
26 name?: string;
27 [key: string]: any;
28 }
29
30 export interface Analytics {
31 track(event: string, properties?: Record<string, any>): void;
32 identify(userId: string, traits?: Omit<UserTraits, 'userId'>): void;
33 page(name?: string, properties?: Record<string, any>): void;
34 group(groupId: string, traits?: Record<string, any>): void;
35 reset(): void;
36 }
37
38 export function createAnalytics(config: AnalyticsConfig): Analytics;
39
40 const analytics: Analytics;
41 export default analytics;
42}Publishing Types#
1// package.json for type package
2{
3 "name": "@types/my-library",
4 "version": "1.0.0",
5 "types": "index.d.ts",
6 "typesVersions": {
7 ">=4.0": { "*": ["ts4.0/*"] },
8 "*": { "*": ["ts3.4/*"] }
9 },
10 "files": [
11 "index.d.ts",
12 "ts4.0/",
13 "ts3.4/"
14 ]
15}
16
17// tsconfig.json for type package
18{
19 "compilerOptions": {
20 "module": "commonjs",
21 "lib": ["es6"],
22 "noImplicitAny": true,
23 "noImplicitThis": true,
24 "strictNullChecks": true,
25 "strictFunctionTypes": true,
26 "types": [],
27 "noEmit": true,
28 "forceConsistentCasingInFileNames": true
29 },
30 "files": ["index.d.ts"]
31}Best Practices#
Structure:
✓ Mirror the library's API structure
✓ Use namespaces for grouping
✓ Export all public types
✓ Document with JSDoc comments
Accuracy:
✓ Match actual library behavior
✓ Use strict type checking
✓ Handle edge cases
✓ Test with real usage
Compatibility:
✓ Support multiple TypeScript versions
✓ Avoid bleeding-edge features
✓ Provide ambient and module forms
✓ Include UMD support if needed
Maintenance:
✓ Version alongside library
✓ Include type tests
✓ Document breaking changes
✓ Accept contributions
Conclusion#
Declaration files bridge JavaScript libraries to TypeScript. Match the library's structure, use appropriate declaration forms (module, namespace, global), and test thoroughly. For popular libraries, consider contributing to DefinitelyTyped. Good type definitions make libraries much more usable in TypeScript projects.