Types for third-party libraries and DefinitelyTyped
Almost no real project is only your code. You install libraries — a date helper, an HTTP client, a
validation library — and the moment you do, a question appears: where do their types come from? Some
libraries ship types themselves; some do not and rely on a community project called DefinitelyTyped; a few
have no types at all and you must cope. This lesson is the practical map of that landscape, so an
npm install never leaves you staring at a red squiggle you do not understand.
Three kinds of library, by how they carry types
Every package you install falls into one of three buckets:
- Ships its own types. Modern libraries written in TypeScript (or that bundle
.d.ts) — Zod, Prisma, Vite, most new packages. Younpm installand types just work; thepackage.jsonhas a"types"field pointing at the bundled declarations. Nothing else to do. - Types live in a separate
@types/*package. Older or JavaScript-first libraries — Express, Lodash, older React — ship no types themselves, but the community maintains them on DefinitelyTyped, published as@types/<name>. You install two packages: the library and its types. - No types anywhere. A small or abandoned package with neither bundled types nor an
@typesentry. You either write a minimal declaration yourself or accept it as untyped.
Knowing which bucket a library is in tells you exactly what to do, so let us take them in turn.
Bucket 1: the library ships its own types
The happy path. You install and import, and your editor immediately knows every function's signature:
npm install zod
import { z } from "zod"; // types come WITH the package — nothing else to install
const schema = z.string(); // fully typed, autocomplete works
How to recognise it: no separate install needed, and the package's package.json has "types": "..." or "typings": "...". This is increasingly the norm — a new library that does not ship types is
now the exception. When it works, you never think about it, which is the point.
Bucket 2: @types/* from DefinitelyTyped
Many widely-used libraries predate TypeScript or are written in plain JavaScript, so they ship no types.
The community fills the gap through DefinitelyTyped — a single enormous repository of hand-written
.d.ts files for thousands of libraries, published to npm under the @types scope:
npm install express # the library (runtime code)
npm install --save-dev @types/express # its types (dev-only — erased at build)
import express from "express"; // now fully typed, thanks to @types/express
const app = express();
app.get("/", (req, res) => res.send("ok")); // req, res are typed
Key facts about @types:
- They are dev dependencies.
--save-dev— types are erased at compile time, so they are not needed at runtime, only while developing and building. - They are separately versioned and community-maintained.
@types/expressversion does not trackexpressversion exactly, and the types are written by contributors, not the library authors — so they can lag, or occasionally be slightly wrong. Usually excellent, but not guaranteed. - The tell that you need one: importing a JavaScript library gives "Could not find a declaration file
for module 'X'. Try
npm i --save-dev @types/X" (TS7016). TypeScript is telling you exactly which bucket you are in and what to install. That error is a signpost, not a wall.
Bucket 3: no types at all
Occasionally a package has neither bundled types nor an @types entry — small, niche, or abandoned. You
import it and get TS7016 with no @types package to install. Three honest options, worst to best:
a. The escape hatch — declare module with any (last resort):
// untyped-libs.d.ts
declare module "some-tiny-lib"; // the whole module becomes `any` — no checking, but it compiles
This silences the error by declaring the module exists with no known shape (everything from it is any).
It gets you moving, but you have turned off checking for that library entirely — the same downside as any
anywhere.
b. Write a minimal declaration for what you actually use (better):
// some-tiny-lib.d.ts — type only the surface you call, leave the rest
declare module "some-tiny-lib" {
export function parse(input: string): { ok: boolean; value: number };
export function format(value: number): string;
}
You do not have to type the whole library — just the two or three functions your code touches. This is
often ten minutes of work and gives you real safety on the parts you use. Read the library's README or its
.js source to get the signatures right.
c. Contribute the types back to DefinitelyTyped (best, if it is a library others use). If you wrote a
good declaration, a pull request to DefinitelyTyped means everyone benefits and you stop maintaining a
local copy. Not always worth it for a one-off, but it is how bucket 2 exists at all — someone did this for
every @types package you have ever installed.
The types versus dependencies distinction, and a common trap
A recurring confusion: which packages go where. The runtime library is a normal dependency; its
@types package is a devDependency. Getting this wrong bites in two ways:
- Putting
@types/*independenciesbloats production installs with types that do nothing at runtime — harmless but wrong. - Forgetting
@types/*entirely means the build fails on a machine that runs a clean install, even though it worked on yours (because you had it locally). CI catching a "Could not find a declaration file" that your laptop did not is exactly this.
And the trap that surprises people most: @types types can be wrong. Because they are hand-written and
separately versioned, a type can claim a function returns string when the current library version returns
string | null. When runtime behaviour contradicts the type, trust the runtime — the type is a
community-maintained description, not ground truth, and occasionally it is stale. This is rare, but when a
"impossible" bug appears at a library boundary, a wrong @types version is worth suspecting.
Check your work
The three buckets. (1) Library ships its own types (install and go); (2) types in a separate
@types/* package on DefinitelyTyped (install both); (3) no types anywhere (declare or write your own).
What DefinitelyTyped is. A community repository of hand-written .d.ts for thousands of untyped
libraries, published under the @types npm scope.
Why @types are dev dependencies. Types are erased at build time — not needed at runtime, only while
developing and compiling.
The error that names the bucket. "Could not find a declaration file for module 'X'... npm i --save-dev @types/X" (TS7016) — a signpost telling you which bucket and what to install.
The three options when no types exist. declare module "x" (whole module any, last resort); write a
minimal declaration for the surface you use (better); contribute to DefinitelyTyped (best, for shared
libraries).
Why @types can be wrong. Hand-written, community-maintained, separately versioned — can lag or
mis-state a signature; trust the runtime when they conflict.
Practice
- Install a modern typed library (Zod) and confirm types work with no
@typesinstall. - Install Express without
@types/express, read the TS7016 error, then install the types and watch it resolve. - For a genuinely untyped package, silence the error with
declare module "name"and note everything from it isany. - Replace that with a minimal
declare moduletyping only the functions you call, and confirm you now get real checking on them. - Move an
@types/*package betweendependenciesanddevDependenciesand reason about which is correct and why. - Find a library's bundled
.d.ts(bucket 1) and its@typesequivalent for another library (bucket 2), and articulate the difference in who maintains each.
Official documentation
- DefinitelyTyped repository — The source of every
@types/*package. - TypeScript — Consuming declaration files — Finding and installing types for a library.
- npm — @types scope search — Check whether a library has an
@typespackage.
Next: adding TypeScript to an existing JavaScript project.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship