TS

TypeScript Fundamentals

17 lessons

Progress0%
1. Introduction to TypeScript
1What is TypeScript?2Setting Up TypeScript
2. Basic Types
1Primitive Types2Interfaces and Type Aliases
3. Functions and Generics
Typed FunctionsGenerics
4. Classes and OOP
ClassesInheritance and Interfaces
5. Advanced Types
Union and Intersection TypesUtility Types
6. Modules and Decorators
ES Modules
7. Decorators
Class & Method DecoratorsProperty & Parameter Decorators
8. Declaration Files
Writing .d.ts Files@types & DefinitelyTyped
9. Advanced Patterns
Conditional Types & inferTemplate Literal Types & satisfies
All Tutorials
TypeScriptDeclaration Files
Lesson 14 of 17 min
Chapter 8 · Lesson 1

Writing .d.ts Files

Declaration files (files with the .d.ts extension) describe the shape of existing JavaScript to the TypeScript compiler without providing any runtime code. Every npm package that ships types — including TypeScript's own standard library — delivers its public API through declaration files.

What Are .d.ts Files?

A .d.ts file contains only ambient declarations: types, interfaces, function signatures, and variable declarations prefixed with the declare keyword. They emit no JavaScript.

typescript
// math-utils.d.ts
declare function add(a: number, b: number): number;
declare const PI: number;

The declare Keyword

declare tells TypeScript "trust me, this exists at runtime." Use it for:

  • declare const / declare let / declare var — global variables
  • declare function — global functions
  • declare class — global classes
  • declare namespace — grouped globals (legacy pattern)
  • declare module — whole module shapes

declare module

Use declare module to type a JavaScript module that has no types of its own:

typescript
// legacy-lib.d.ts
declare module "legacy-lib" {
  export function doThing(x: string): void;
  export const version: string;
}

declare global

Inside a module file (any file with an import or export), use declare global to add or extend global scope:

typescript
export {}; // makes this a module
declare global {
  interface Window {
    analytics: AnalyticsInstance;
  }
}

Triple-Slash Directives

Before ES module imports existed, /// <reference> directives linked declaration files together. They are still useful for referencing types packages and lib files:

typescript
/// <reference types="node" />
/// <reference path="./custom-globals.d.ts" />

When to Write vs. Use @types

  • If a library ships its own types (check its package.json for a "types" or "typings" field), no extra work is needed.
  • If DefinitelyTyped has a package (@types/lodash), install that.
  • Write a custom .d.ts file only for in-house JS that will never be published, or to patch incomplete community types.

Understanding .d.ts files demystifies what npm packages are actually delivering and gives you the tools to fill gaps in third-party type coverage.

Code Examples

Declaring a Module Without Typestypescript
// File: src/types/legacy-analytics.d.ts
// Provides types for the untyped "legacy-analytics" npm package

declare module "legacy-analytics" {
  export interface TrackOptions {
    userId: string;
    event: string;
    properties?: Record<string, unknown>;
  }

  export function track(options: TrackOptions): void;
  export function identify(userId: string, traits?: Record<string, unknown>): void;
  export function page(name: string): void;

  export const version: string;

  // Default export — the analytics singleton
  const analytics: {
    track: typeof track;
    identify: typeof identify;
    page: typeof page;
  };
  export default analytics;
}

// ----- Usage in application code -----
// Now TypeScript fully understands "legacy-analytics"
import analytics, { track } from "legacy-analytics";

track({ userId: "u-1", event: "signup" });
console.log("Module declaration working correctly");

The declare module block gives TypeScript a complete type signature for a JavaScript package that ships no .d.ts files. After creating this file, all imports from that package are fully type-checked.

Augmenting an Existing Module Declarationtypescript
// File: src/types/express-augment.d.ts
// Augments Express's Request type to include our custom user property

import "express";

declare module "express" {
  interface Request {
    currentUser?: {
      id: string;
      role: "admin" | "user" | "guest";
    };
  }
}

// ----- Simulated usage -----
// In a real Express app you would import { Request, Response } from "express"
// Here we demonstrate the shape of the augmented interface

interface Request {
  currentUser?: {
    id: string;
    role: "admin" | "user" | "guest";
  };
  path: string;
}

function authMiddleware(req: Request): void {
  if (!req.currentUser) {
    console.log("Unauthenticated request to", req.path);
    return;
  }
  console.log(`User ${req.currentUser.id} (${req.currentUser.role}) accessing ${req.path}`);
}

authMiddleware({ path: "/dashboard", currentUser: { id: "u-99", role: "admin" } });
authMiddleware({ path: "/login" });

Module augmentation extends an existing type declaration without forking it. The import at the top makes this a module file, and declare module 'express' opens the existing declaration for extension.

Global Ambient Declarationstypescript
// File: src/types/globals.d.ts
// Declares globals injected by the build tool (e.g., Webpack DefinePlugin)

declare const __APP_VERSION__: string;
declare const __BUILD_DATE__: string;
declare const __DEV__: boolean;

// Extend the built-in Window interface
declare global {
  interface Window {
    dataLayer: Array<Record<string, unknown>>;
    gtag: (...args: unknown[]) => void;
  }
}

// Extend NodeJS global (useful in Node environments)
declare namespace NodeJS {
  interface ProcessEnv {
    DATABASE_URL: string;
    JWT_SECRET: string;
    NODE_ENV: "development" | "production" | "test";
  }
}

// ----- Simulated runtime values -----
const appVersion = __APP_VERSION__;
const buildDate = __BUILD_DATE__;
const isDev = __DEV__;

console.log(`App ${appVersion} built on ${buildDate}`);
console.log(`Dev mode: ${isDev}`);

Global ambient declarations tell TypeScript about variables injected at build time or attached to global objects. declare global inside a module file safely augments the global scope without polluting other modules.

Quick Quiz

1. What keyword prefix is used for all declarations inside a .d.ts file?

2. What must you add to make declare global work inside a module file?

3. Which triple-slash directive is used to reference a @types package?

4. When should you write a custom .d.ts file instead of using @types?

Was this lesson helpful?

PreviousNext