TypeScript Declaration Files
Write .d.ts files, publish typed npm packages, and create type stubs.
TL;DR
- 01Create .d.ts files to provide types for JavaScript libraries.
- 02Use declare keyword to define types without implementation.
- 03Publish types alongside your npm package for TypeScript users.
Tips
- 01Always include declaration files with your npm package or publish to DefinitelyTyped — TypeScript users will appreciate the type safety.
Warnings
- 01Keep declaration files accurate and in sync with implementation — incorrect types are worse than no types at all.
Writing Declaration Files
- Create .d.ts files to define types for JavaScript code.
// mylib.d.ts export function greet(name: string): string; export interface User { id: number; name: string; } - Declaration files contain only type definitions, no implementation.
// index.d.ts export class Calculator { add(a: number, b: number): number; subtract(a: number, b: number): number; } - Use declare to define types for existing JavaScript globals.
declare global { interface Window { myGlobal: string; } } - Declare ambient variables that exist at runtime but not in source.
declare const __VERSION__: string; declare function require(module: string): any; - Use declare module for UMD or non-ES library entry points.
export as namespace MyLib; export function init(options: Options): void; export interface Options { debug?: boolean; }
Module Declaration
- Declare types for entire modules or namespaces.
declare module "my-library" { export function process(data: any): any; export const version: string; } - Augment existing modules with additional types.
declare module "express" { interface Request { userId?: number; } } - Use triple-slash directives to reference other declaration files.
/// <reference path="./types.d.ts" /> /// <reference types="node" /> - Organize types with namespaces for large libraries.
declare namespace MyLib { interface Config { } function init(config: Config): void; } - Use a wildcard module declaration for file imports like CSS or SVG.
declare module "*.svg" { const content: string; export default content; } declare module "*.css" { const styles: Record<string, string>; export default styles; }
Package Configuration
- Point to declaration files in package.json with the types field.
{ "name": "my-library", "version": "1.0.0", "main": "dist/index.js", "types": "dist/index.d.ts" } - Emit declaration files from TypeScript during build.
{ "compilerOptions": { "declaration": true, "declarationDir": "./dist", "outDir": "./dist" } } - Use typesVersions to support different TypeScript versions.
{ "typesVersions": { "<=4.0": { "*": ["ts4.0/*"] }, ">=4.1": { "*": ["*"] } } } - Use exports map with types conditions for dual CJS/ESM packages.
{ "exports": { ".": { "import": { "types": "./dist/esm/index.d.ts", "default": "./dist/esm/index.js" }, "require": { "types": "./dist/cjs/index.d.ts", "default": "./dist/cjs/index.js" } } } } - Generate declaration maps for go-to-definition in source.
{ "compilerOptions": { "declarationMap": true, "declaration": true, "sourceRoot": "./src" } }
Publishing to DefinitelyTyped
- Contribute type definitions for popular untyped libraries.
DefinitelyTyped/types/ library-name/ index.d.ts package.json tsconfig.json tests.ts - Test declaration files with sample code before submitting.
// tests.ts import * as lib from "./index"; const result: string = lib.process("test"); const version: string = lib.version; - Include a package.json in the types folder with the correct shape.
{ "name": "@types/library-name", "version": "1.0.0", "typings": "index.d.ts" } - Add a tsconfig.json to validate declarations during CI.
{ "compilerOptions": { "module": "commonjs", "lib": ["es6"], "noImplicitAny": true, "noImplicitThis": true, "strictNullChecks": true, "strictFunctionTypes": true } } - Run dtslint to catch type errors before opening a pull request.
npm install -g dtslint dtslint types/library-name
Best Practices
- Keep declaration files in sync with implementation.
// Don't declare types that don't match the implementation export function add(a: number, b: number): number; // Should match: const add = (a, b) => a + b; - Export only public APIs and hide internal helpers.
// Correct: public types are exported export interface PublicAPI { } // Wrong: internal types shouldn't be exported // export interface _InternalHelper { } - Include JSDoc comments for better IDE support.
/** * Processes data asynchronously * @param data - The input data to process * @returns A promise that resolves with the result */ export function processAsync(data: any): Promise<any>; - Use semver versioning for types to avoid breaking changes.
# Bump patch for additions, minor for new overloads, major for removals npm version patch # safe: new optional param npm version major # breaking: removed export - Avoid using any in declaration files — prefer unknown or generics.
// Weak: loses type information export function parse(input: string): any; // Better: let the caller specify the return type export function parse<T>(input: string): T;
FAQ
A .d.ts file contains only type declarations with no runtime code — it uses the declare keyword to describe the shape of existing JavaScript without any implementation. Regular .ts files compile to JavaScript, while declaration files are consumed only by the TypeScript compiler for type checking.
Add a "types" or "typings" field in your package.json pointing to your main .d.ts file, e.g. "types": "./dist/index.d.ts". If your types file has the same name as your "main" entry but with a .d.ts extension, TypeScript will find it automatically without the explicit field.
Publish to DefinitelyTyped (@types/yourpackage) when you're adding types for a third-party JavaScript library you don't own or maintain. If you own the package, bundle the .d.ts files directly — it keeps types versioned in sync with the implementation and simplifies the install process for consumers.
Use export = myFunction syntax in your declaration file instead of export default, since export default doesn't match how CommonJS modules work at runtime. Consumers using import myFn from 'pkg' will need esModuleInterop: true in their tsconfig to use export = style declarations.
Yes — set "declaration": true in your tsconfig.json and TypeScript will emit .d.ts files alongside your compiled output automatically. This is the recommended approach for TypeScript-authored libraries; hand-written declarations are only necessary when typing an existing JavaScript codebase.