Skip to content
ExponaBLOG
JAVASCRIPT & TYPESCRIPT · DAY 060 / 100

Typing third-party libraries in JavaScript & TypeScript

When packages don't ship types.

Expona Admin6 min read

Typing third-party libraries

One of the trickiest moments in a TypeScript project arrives when you reach for a popular npm package and discover it has no type definitions. You install it, import it, and immediately see any everywhere—or worse, an error that tells you the module doesn't exist in TypeScript's eyes at all. Typing third-party libraries isn't just a nice-to-have; it's essential for keeping your codebase type-safe and maintainable. In this post, I'll walk through the practical strategies I use when packages don't ship types, from hunting down existing definitions to writing your own and even contributing them back to the community.

Check DefinitelyTyped — @types packages for many libraries

The first and easiest step is always to check whether someone has already solved the problem for you. DefinitelyTyped is a community-driven repository of TypeScript type definitions for JavaScript libraries that don't include their own. If a package has types on DefinitelyTyped, they're published under the @types namespace on npm.

Start by searching npm for @types/[package-name]. For example, if you're using lodash, you'd look for @types/lodash. If it exists, the installation is trivial:

typescript
npm install --save-dev @types/lodash

That's often all you need. Once installed, TypeScript will automatically pick up the types, and your imports work with full intellisense and type checking.

I've had excellent results with DefinitelyTyped for popular libraries like React, Express, Node.js, and countless others. The maintainers there are usually responsive, and the coverage is broad. A quick tip: if you're unsure whether types exist, check the official DefinitelyTyped website or use npm's search from the terminal. It's faster than guessing.

That said, not everything is on DefinitelyTyped. Newer packages, highly specialized libraries, or niche tools often have no community types yet. That's when you move to plan B: writing your own.

Write declarations — A small .d.ts file for the parts you use

When no @types package exists, I typically create a simple .d.ts file in my project. You don't need to type the entire library—just the parts you actually use. This is pragmatic and keeps the work manageable.

Let's say you're using a hypothetical package called color-picker that has no types. You're only using two functions: openPicker and parseColor. Create a file at the root of your project, maybe in a types folder:

typescript
// types/color-picker.d.ts

declare module 'color-picker' {
  export interface ColorResult {
    hex: string;
    rgb: [number, number, number];
    alpha: number;
  }

  export function openPicker(options: {
    defaultColor?: string;
    onSelect?: (color: ColorResult) => void;
  }): void;

  export function parseColor(input: string): ColorResult | null;
}

Now when you import from color-picker, TypeScript knows exactly what's available and what types to expect:

typescript
import { openPicker, parseColor } from 'color-picker';

const result = parseColor('#ff5733');
if (result) {
  console.log(result.hex); // TypeScript knows this is a string
}

Make sure your tsconfig.json includes this types folder. A typical configuration looks like:

typescript
{
  "compilerOptions": {
    "typeRoots": ["./node_modules/@types", "./types"]
  }
}

I've used this approach countless times when integrating smaller or internal packages. The effort is minimal compared to the type safety you gain. You're not committing to typing the entire library—only what you need today. As your usage expands, you can gradually add more types to the declaration file.

Module augmentation — Extend existing types, like adding user to Express requests

Sometimes the library already has types, but they don't quite fit your use case. You might want to attach custom properties to existing objects, or override behaviors for your specific application. Module augmentation lets you extend those types without modifying the original package.

A real-world example: I often use Express with TypeScript. Express has types, but the Request object doesn't know about custom properties you've added in middleware. If you set req.user in an authentication middleware, TypeScript will complain when you try to access it in a route handler.

Module augmentation solves this elegantly. Create a file like types/express.d.ts

typescript
// types/express.d.ts

import { Request } from 'express';

declare global {
  namespace Express {
    interface Request {
      user?: {
        id: string;
        email: string;
        role: 'admin' | 'user';
      };
    }
  }
}

export {};

Now, anywhere in your Express app, you can safely access req.user with full type checking:

typescript
app.get('/profile', (req, res) => {
  if (req.user) {
    console.log(req.user.email); // TypeScript is happy
    res.json({ userId: req.user.id });
  }
});

I've also used module augmentation with other libraries. For instance, if you're extending Redux state or adding properties to window, module augmentation is the right tool. It keeps your types close to where you use them and doesn't require forking the original package.

One important detail: make sure to export an empty object at the end of your augmentation file (as shown above). Without it, TypeScript may not recognize the file as a module, and the augmentation might not apply.

Contribute back — Share good declarations upstream

If you've written solid type definitions for a popular library that doesn't have types yet, consider contributing them to DefinitelyTyped. It's not as intimidating as it sounds, and the maintainers there are welcoming to contributors.

The process is straightforward. Fork the DefinitelyTyped repository, create a new folder under types/[package-name], and follow their template structure. You'll need:

  • An index.d.ts with your type declarations.
  • A package.json file.
  • Tests in a [package-name]-tests.ts file to verify your types work correctly.
  • A README.md with basic information.

I contributed types for a smaller library once, and the review process was thorough but constructive. The maintainers care about quality and consistency. If you're unsure about something, they'll guide you.

Why bother? Because it helps the entire community. Someone else will install your types next week and save hours of debugging. It also solidifies your understanding of the library and TypeScript itself. Plus, contributing to open-source is always a good look on your resume.

Common mistakes

Over-typing too early: When writing your own .d.ts files, it's tempting to type every function and property. Resist that urge. Type what you use, and add more as you need it. This keeps your maintenance burden low.

Forgetting the export statement in augmentations: I've wasted time on this. If you're using module augmentation, always end the file with export {}; This tells TypeScript the file is a module, not just ambient declarations.

Not updating types when the library updates: If you maintain a custom .d.ts file, keep it in sync with the library's actual API. I usually add a comment at the top with the library version it covers to avoid confusion.

Assuming @types covers everything: Sometimes a @types package is incomplete or outdated. Don't hesitate to layer module augmentation on top to fill gaps. DefinitelyTyped is maintained by humans and sometimes lags behind actual releases.

Key takeaways

  • Always check npm for @types/[package-name] first—DefinitelyTyped covers hundreds of libraries and saves you work.
  • When types don't exist, write a minimal .d.ts file for only what you use. You can expand it later.
  • Use module augmentation to extend existing types without modifying the original package—it's perfect for adding custom properties to Express requests or global objects.
  • If you write good types for a library, consider contributing them to DefinitelyTyped. The community benefits, and you build credibility.
  • Keep your type declarations maintainable. Document them, version them, and update them as the library evolves.

ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles