AniUI Academy

Modules and Declaration Files

Exporting and importing types alongside values, the import type syntax, and how a .d.ts file describes the shape of code without providing any implementation.

9 min read

Modules in TypeScript are the same ES modules you already know from JavaScript — import and export — with the type system layered seamlessly on top, plus one addition specific to types themselves: a way to describe a module's shape without any implementation at all.

Exporting and importing types

Types export and import exactly like values, using the same syntax:

// user.ts
export interface User {
  name: string;
  age: number;
}
 
export function createUser(name: string, age: number): User {
  return { name, age };
}
// main.ts
import { User, createUser } from "./user";
 
const user: User = createUser("Amara", 29);

Nothing new so far — a type and a value were exported from the same file and imported together, the same statement handling both.

import type: making the type-only nature explicit

When an import is used purely as a type — never as a runtime value — import type says so explicitly:

import type { User } from "./user";
 
function printUser(user: User) {
  console.log(user.name);
}

Because User never appears as a runtime value anywhere in this file, the entire import type statement is erased when compiling to JavaScript — there is nothing left to import, so nothing is emitted. A regular import { User } from "./user" (without type) would, in most configurations, also be erased automatically once the compiler notices User is only ever used as a type — but writing import type makes that intent explicit rather than relying on the compiler to figure it out, which matters more once a file mixes type-only and value imports:

import { createUser } from "./user";  // a real runtime import — createUser is a function
import type { User } from "./user";   // erased entirely — User is type-only

This split is required in stricter configurations (notably isolatedModules, common with fast transpilers that process one file at a time and can't always tell on their own whether an import is type-only) and is good practice regardless, since it documents the distinction for a reader too.

Declaration files: describing a shape with no implementation

A .d.ts file contains only type declarations — no runnable code at all. It exists to describe the shape of something the compiler needs to check against, without needing (or being able) to see how it's actually implemented:

// math-utils.d.ts
export function add(a: number, b: number): number;
export function multiply(a: number, b: number): number;

This is exactly what happens when you compile a regular .ts file with declaration emission turned on (declaration: true in tsconfig.json) — the compiler produces a matching .d.ts file alongside the .js output, stripping every implementation and keeping only the shapes. This is how a published TypeScript library ships type information to its consumers: the shipped code is plain JavaScript, and a .d.ts file next to it tells TypeScript what that JavaScript's types actually are.

Typing an untyped JavaScript module

Sometimes you depend on a plain JavaScript module with no types of its own — older code, or a library that never adopted TypeScript. A hand-written .d.ts file lets you describe its shape yourself:

// vendor-analytics.d.ts
declare module "vendor-analytics" {
  export function track(event: string, properties?: Record<string, unknown>): void;
}
// somewhere in your app
import { track } from "vendor-analytics";
 
track("lesson_completed", { lessonId: "modules-and-declaration-files" });

declare module tells TypeScript "trust me about this module's shape" — there's no checking against a real implementation, because there isn't one available to check against. This is exactly the mechanism behind the next lesson, on typing third-party packages properly.

Try it yourself

Try it yourself
Loading playground...

What to remember

  • Types export and import through the same import/export syntax as values; import type makes a type-only import explicit and guarantees it's erased.
  • A .d.ts file describes shapes only — no runtime implementation — and is what a compiled TypeScript library ships alongside its plain JavaScript output.
  • declare module "name" { ... } lets you hand-write types for an untyped JavaScript module you depend on.
  • A .d.ts file is only as accurate as whoever wrote it — the compiler cannot verify it against a real implementation it never sees.

Check yourself

4 questions · pass 3/4 to unlock Working with Third-Party Types

up to 50
  1. 1.What does import type { User } from "./types"; do differently from a regular import?

  2. 2.What does a .d.ts file (a declaration file) contain?

  3. 3.What is the purpose of a package's @types/package-name counterpart on npm?

  4. 4.In an ES module, what makes a file a module rather than a script, as far as TypeScript and JavaScript are concerned?

4 left to answer