Lesson 21 of 25
Working with Third-Party Types
DefinitelyTyped and the @types scope, reading a library's type errors, and augmenting a type you don't own through declaration merging.
Real applications depend on dozens of third-party packages, and not every one was written in TypeScript or ships its own types. This lesson covers the practical side of that: finding types for a package that lacks them, and extending a type you don't own.
DefinitelyTyped and the @types scope
DefinitelyTyped is a large, community-maintained repository of .d.ts
declaration files for JavaScript packages that don't include their own
types. Its contents are published to npm under the @types scope:
npm install lodash
npm install --save-dev @types/lodashOnce both are installed, using lodash is fully typed with no further
configuration or import changes needed — TypeScript's module resolution
automatically checks node_modules/@types/<package-name> when resolving
types for a package, alongside the package's own node_modules entry.
import chunk from "lodash/chunk";
const groups = chunk([1, 2, 3, 4, 5], 2);
// groups: number[][] — typed correctly, thanks to @types/lodashTypes shipped by the package itself
Many modern packages ship their own types directly rather than relying on a
separate @types package — indicated by a "types" (or "typings") field
in the package's own package.json. For these, no separate installation is
needed at all; the types travel with the package itself. Installing
@types/package-name on top of one of these is not just redundant, it's a
real, if usually minor, hazard — two separate sets of declarations for the
same package can drift and conflict.
A quick, reliable way to check which situation you're in: look inside
node_modules/package-name/package.json for a "types" field, or simply
try importing the package and see whether your editor already shows types
with nothing extra installed.
When there are no types at all
If neither the package nor DefinitelyTyped provides types, a minimal hand-written declaration (from the previous lesson) is the fallback:
// vendor-widget.d.ts
declare module "vendor-widget" {
export function render(target: string): void;
}This unblocks usage but comes with the honesty caveat from the previous lesson — the compiler trusts this declaration completely and has no way to verify it matches the package's real behaviour.
Augmenting a type you don't own
Declaration merging (covered with interfaces, several lessons back) isn't limited to types you wrote yourself — re-declaring an interface from a library, using the same name inside the same module, merges your additions into the original:
// express.d.ts — augmenting Express's Request type with a custom field
import "express";
declare module "express" {
interface Request {
userId?: string; // added by your own authentication middleware
}
}// middleware.ts
import type { Request, Response, NextFunction } from "express";
function auth(req: Request, res: Response, next: NextFunction) {
req.userId = "abc123"; // fine — Request now includes userId, everywhere it's imported
next();
}This is precisely how a library that supports plugins or middleware lets consumers extend its core types without needing to fork or modify the library's own source — the augmentation lives entirely in your own codebase.
Try it yourself
What to remember
- Check for a matching
@types/package-nameon npm first, for a package with no types of its own — TypeScript resolves it automatically once installed. - Many modern packages ship their own types directly; installing a separate
@typespackage on top of one risks conflicting declarations. - A hand-written
.d.tswithdeclare moduleis the fallback when neither exists, but the compiler cannot verify it against the real implementation. - Declaration merging lets you augment a library's own interfaces (like Express's
Request) with additional properties, without modifying the library's source.
Check yourself
4 questions · pass 3/4 to unlock Configuring tsconfig.json and Strictness
1.A package on npm ships no types of its own. What's the standard first step to get typed access to it?
2.How does TypeScript know to use a package's @types/package-name declarations automatically?
3.What is a realistic risk when a package ships its own types and you also have @types/package-name installed?
4.What does declaration merging let you do with a type you don't own, such as one from a library?
4 left to answer