AniUI Academy

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.

8 min read

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/lodash

Once 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/lodash

Types 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

Try it yourself
Loading playground...

What to remember

  • Check for a matching @types/package-name on 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 @types package on top of one risks conflicting declarations.
  • A hand-written .d.ts with declare module is 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

up to 50
  1. 1.A package on npm ships no types of its own. What's the standard first step to get typed access to it?

  2. 2.How does TypeScript know to use a package's @types/package-name declarations automatically?

  3. 3.What is a realistic risk when a package ships its own types and you also have @types/package-name installed?

  4. 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