Question

Understanding .d.ts Declaration Files in TypeScript

Question

I want to understand how .d.ts declaration files work in TypeScript.

I was told that .d.ts files are similar to .h header files in C and C++, but they do not seem to behave the same way. Right now, I am struggling to understand how to use them correctly.

It seems that I cannot place my .js or .ts implementation code inside a .d.ts file, so it looks like a project may need all three file types: .js, .ts, and .d.ts.

My questions are:

  • What is the relationship between these three file types?
  • How do I use a *.d.ts file? Does it mean I can permanently delete the *.ts file?
  • If so, how does a *.d.ts file know which JavaScript file it describes?

Short Answer

By the end of this page, you will understand what .d.ts files are, how they differ from .ts and .js files, and when each file type is needed. You will also learn how TypeScript connects declaration files to JavaScript modules and why .d.ts files describe code rather than implement it.

Concept

What a .d.ts file is

A .d.ts file is a TypeScript declaration file. Its job is to describe the shape of code without containing the actual implementation.

That means a declaration file can say:

  • a function exists
  • what parameters it accepts
  • what it returns
  • what objects, classes, or constants are available

But it does not contain the executable logic.

The role of each file type

.ts

A .ts file contains TypeScript source code.

  • It can contain implementation
  • It can contain types
  • It is usually compiled into .js

Example:

export function add(a: number, b: number): number {
  return a + b;
}

.js

A .js file contains JavaScript runtime code.

  • This is what actually runs
  • Browsers and Node.js execute this code

Example:

export function add(a, b) {
  return a + b;
}

.d.ts

A .d.ts file contains only type declarations.

  • It does not run
  • It is used by the TypeScript compiler and editor tooling
  • It describes JavaScript or compiled TypeScript code

Example:

export declare function add(a: number, b: number): number;

Why declaration files matter

TypeScript needs type information to give you:

  • autocomplete
  • error checking
  • better refactoring
  • documentation-like hints in editors

If a library is written in plain JavaScript, TypeScript cannot automatically know its types. A .d.ts file fills that gap by describing the library.

Common relationships between files

There is not always a 1-to-1 relationship where every project must have all three.

Pattern 1: TypeScript project

You write .ts files. TypeScript compiles them to .js. It may also generate .d.ts files.

math.ts  ->  math.js
         ->  math.d.ts

Here:

  • math.ts is the source
  • math.js is the runtime output
  • math.d.ts is the type description for consumers

Pattern 2: JavaScript library with types

You write JavaScript manually and add a .d.ts file.

math.js
math.d.ts

Here:

  • math.js runs
  • math.d.ts describes it

There is no .ts file required.

Pattern 3: TypeScript application only

You write .ts and compile to .js, but you never publish declarations separately.

app.ts -> app.js

A separate .d.ts file may not be necessary at all.

Can .d.ts replace .ts?

Usually, no.

A .d.ts file describes code, but it does not implement code. If you delete your .ts file and only keep the .d.ts, you keep the type information but lose the actual program logic.

This means:

  • .d.ts can replace .ts only as a description
  • it cannot replace .ts as implementation

If you already have compiled JavaScript somewhere, then a .d.ts file may be enough for TypeScript users to understand the API.

How TypeScript knows which .js file a .d.ts file belongs to

Usually, this is based on the module name or file path.

For example:

lib/
  math.js
  math.d.ts

If code imports ./math, TypeScript will use math.d.ts for type information and math.js at runtime.

Example:

import { add } from './math';

At development time:

  • TypeScript reads math.d.ts

At runtime:

  • JavaScript loads math.js

The declaration file does not contain a special pointer to the JavaScript file. Instead, the compiler resolves both using the module system and file naming conventions.

Important idea

A .d.ts file is like a contract or interface description for code that exists somewhere else.

It tells TypeScript:

  • what names exist
  • what types those names have
  • how other files may use them

But the actual behavior still comes from .js or compiled .ts output.

Mental Model

Think of these files like a product in a box:

  • .js is the actual product you can use
  • .ts is the product being designed with detailed instructions
  • .d.ts is the label on the box describing what is inside

The label is useful, but the label is not the product.

Another way to think about it:

  • .js = the working machine
  • .d.ts = the blueprint of the machine's public controls
  • .ts = the original source used to build the machine

A blueprint can describe a machine, but it cannot run by itself.

Take Quiz

Syntax and Examples

Basic declaration syntax

A declaration file uses declarations instead of implementations.

Function declaration

export declare function greet(name: string): string;

This says:

  • there is a function called greet
  • it takes a string
  • it returns a string

But there is no function body.

Variable declaration

export declare const version: string;

Interface declaration

export interface User {
  id: number;
  name: string;
}

Class declaration

export declare class Person {
  constructor(name: string);
  name: string;
  sayHello(): string;
}

Example: JavaScript file plus declaration file

math.js

export function add(a, b) {
  return a + b;
}

math.d.ts

export declare function add(a: number, b: number): number;

app.ts

import { add } from './math';

const result = add(2, 3);
console.log(result);

TypeScript understands the types from math.d.ts, while JavaScript executes math.js.

Example: TypeScript source generating declarations

math.ts

export function add(a: number, b: number): number {
  return a + b;
}

This can compile to:

generated math.js

export function add(a, b) {
  return a + b;
}

generated math.d.ts

export declare function add(a: number, b: number): number;

In this case, you usually write only the .ts file yourself. The compiler can produce the .js and .d.ts files.

Declaring an external module

Sometimes you declare types for a package by module name:

declare module 'my-library' {
  export function add(a: number, b: number): number;
}

This tells TypeScript what is available when importing from 'my-library'.

Step by Step Execution

Example

Suppose you have these files:

logger.js

export function log(message) {
  return `[LOG] ${message}`;
}

logger.d.ts

export declare function log(message: string): string;

main.ts

import { log } from './logger';

const result = log('Hello');
console.log(result);

What happens step by step

1. TypeScript reads main.ts

It sees:

import { log } from './logger';

So it needs to know what ./logger exports.

2. It looks for type information

TypeScript finds logger.d.ts and reads:

export declare function log(message: string): string;

Now it knows:

  • log is a function
  • it expects a string
  • it returns a string

3. Type checking happens

This is valid:

const result = log('Hello');

This would be an error:

const result = log(123);

because 123 is not a string.

4. Runtime uses JavaScript

When the program actually runs, TypeScript types are gone. The real executed code comes from logger.js.

That file contains:

export function log(message) {
  return `[LOG] ${message}`;
}

5. Output is produced

The result becomes:

[LOG] Hello

Key takeaway

  • .d.ts helps during development and type checking
  • .js is what actually runs

Real World Use Cases

1. Adding types to a JavaScript library

Many libraries are written in JavaScript but still want to support TypeScript users. A .d.ts file lets the library provide type information without rewriting the whole codebase in TypeScript.

2. Publishing a TypeScript package

A package author writes .ts source files and publishes:

  • compiled .js
  • generated .d.ts

Consumers get both runtime code and type safety.

3. Typing legacy code

In older projects, teams often have lots of JavaScript already. They can gradually add .d.ts files to describe existing modules and improve editor support before fully migrating to TypeScript.

4. Describing browser globals or external APIs

Declaration files can describe values that exist at runtime but are not defined directly in your current source, such as:

  • global variables injected by a script
  • APIs provided by a host environment
  • third-party plugins

5. Internal shared contracts in large codebases

Teams may use declarations to describe public APIs between packages, especially in monorepos or shared libraries.

Take Quiz

Real Codebase Usage

Common patterns in real projects

Generate .d.ts from .ts

This is the most common pattern in TypeScript libraries. Developers write normal TypeScript, then let the compiler generate declarations.

Benefits:

  • avoids duplicating types manually
  • keeps declarations in sync with implementation
  • reduces mistakes

Write .d.ts manually for JavaScript code

This is common when:

  • a project is still mostly JavaScript
  • only type hints are needed
  • migration to TypeScript is gradual

Describe package public APIs

Many packages expose only selected functions and classes. Their declaration files describe the exported API clearly, which acts like documentation and type safety at the same time.

Use declarations for globals

Example:

declare const API_URL: string;

This is useful when a bundler, environment, or script injects something at runtime.

Validation and contract clarity

Developers use type declarations to make function usage clearer:

export interface Config {
  host: string;
  port: number;
}

export declare function connect(config: Config): void;

This helps catch incorrect usage before runtime.

Practical advice

If you own the TypeScript source, prefer writing .ts and generating .d.ts.

If you only have JavaScript implementation, provide .d.ts manually.

Common Mistakes

1. Putting implementation code inside .d.ts

A declaration file should not contain real executable logic.

Incorrect

export function add(a: number, b: number): number {
  return a + b;
}

Correct

export declare function add(a: number, b: number): number;

2. Thinking .d.ts can run by itself

A .d.ts file is not runtime code. If you only keep a declaration file and remove the actual JavaScript or compiled output, there is nothing to execute.

3. Letting declarations get out of sync with implementation

math.js

export function add(a, b) {
  return String(a + b);
}

wrong math.d.ts

export declare function add(a: number, b: number): number;

This is dangerous because the declaration says number, but the implementation returns a string.

How to avoid it

  • generate declarations from .ts when possible
  • update declaration files whenever implementation changes

4. Assuming every file needs a matching .d.ts

Most applications do not need manual declaration files for every module. Often, .ts source files already provide the types.

5. Confusing declare with definition

declare means “this exists somewhere else.” It does not create the value at runtime.

Example

declare const apiKey: string;
console.log(apiKey);

This type-checks only if apiKey truly exists at runtime somewhere else.

6. Using the wrong module shape

If the JavaScript exports do not match the declaration exports, imports will be confusing or broken.

For example, declaring a default export when the JS file uses named exports can cause problems.

Comparisons

.ts vs .js vs .d.ts

File typeContains implementation?Contains types?Runs directly?Common use
.tsYesYesNo, usually compiled firstMain TypeScript source
.jsYesSometimes via JSDoc, but not TypeScript syntaxYesRuntime code
.d.tsNoYesNoType declarations only

.d.ts vs C/C++ header files

ConceptC/C++ .hTypeScript .d.ts
Describes code structureYesYes
Contains executable implementationSometimes inline or via macros/templatesNo
Used for compile-time informationYesYes
Used at runtimeNoNo
Tied to implementation filesOften paired with .c/.cppOften paired with .js or generated from .ts

The comparison to header files is helpful, but not exact. TypeScript declarations are more specifically about type information and API shape.

Manual .d.ts vs generated .d.ts

ApproachBest whenProsCons
Manual .d.tsYou have JavaScript codeWorks without rewriting codeCan get out of sync
Generated .d.tsYou write TypeScript sourceAccurate and maintainableRequires TypeScript source
Take Quiz

Cheat Sheet

Quick reference

What each file does

  • .ts = TypeScript source
  • .js = runtime JavaScript
  • .d.ts = type declarations only

Core rule

A .d.ts file describes code. It does not implement code.

Common file relationships

source.ts -> output.js + output.d.ts

or

library.js + library.d.ts

Declaration examples

export declare function greet(name: string): string;
export declare const version: string;
export interface User {
  id: number;
  name: string;
}

When you need .d.ts

  • when typing JavaScript modules
  • when publishing types for a library
  • when declaring globals or external modules

When you usually do not need manual .d.ts

  • when writing a normal TypeScript app and not publishing reusable APIs
  • when .ts source already provides the needed types

Important rules

  • no function bodies in .d.ts
  • declare means the real value exists elsewhere
  • declarations must match runtime behavior
  • TypeScript uses module resolution to connect .d.ts to imports

Remember

If you delete .ts and keep only .d.ts, you keep the type information, not the implementation.

FAQ

What is a .d.ts file in TypeScript?

A .d.ts file is a declaration file that describes the types and exported API of code without containing the actual implementation.

Do I need both .ts and .d.ts files?

Not always. If you write TypeScript, your .ts files may be enough during development, and .d.ts files can be generated when needed. If you write JavaScript, you may add .d.ts files manually.

Can a .d.ts file replace a .ts file?

No, not as implementation. A .d.ts file only provides type information. You still need JavaScript or compiled TypeScript code to run the program.

How does TypeScript match a .d.ts file to a .js file?

Usually through the same module name or file path. For example, importing ./math lets TypeScript use math.d.ts for types and math.js at runtime.

Are .d.ts files only for libraries?

No. They are common in libraries, but they can also be used in applications for globals, legacy JavaScript modules, or third-party integrations.

Should I write .d.ts files by hand?

Only when needed. If your project is written in TypeScript, it is usually better to generate declaration files automatically.

Why can't I put normal code inside a .d.ts file?

Because declaration files are meant only for describing shapes and types. They are not executable modules with implementation logic.

Take Quiz

Mini Project

Description

Create a small JavaScript utility module and a matching TypeScript declaration file. This project demonstrates the real purpose of .d.ts files: giving TypeScript type information for JavaScript code that already exists and runs.

Goal

Build a JavaScript module that exports a few utility functions, then add a .d.ts file so a TypeScript file can import and use that module with full type checking.

Requirements

  1. Create a utils.js file with at least two exported functions.
  2. Create a matching utils.d.ts file that describes those exports.
  3. Create a main.ts file that imports and uses the JavaScript module.
  4. Make sure the declarations match the real JavaScript behavior.
  5. Include at least one function that returns a typed object.
Take Quiz

Keep learning

Related questions

View more