Contributing to DefinitelyTyped: A Practical Guide for Intermediate Developers
Introduction
The DefinitelyTyped repository (and the @types namespace on npm) is the central place where the TypeScript community publishes type definitions for JavaScript libraries that don't ship their own types. Contributing to DefinitelyTyped is a high-impact way to improve the TypeScript ecosystem: your PRs help thousands of developers avoid runtime bugs, improve editor experience, and enable safer refactors.
This guide is written for intermediate TypeScript developers who already understand types, modules, and basic tooling. You will learn how to find packages that need types, author high-quality declaration files (.d.ts), write tests that validate runtime shapes, run local checks, and submit a clean PR to DefinitelyTyped. We'll also cover advanced topics like dealing with module augmentation, supporting multiple module systems, maintaining types in a monorepo, and optimizing the development feedback loop.
Throughout this tutorial you will find practical code snippets, step-by-step instructions for common scenarios, troubleshooting tips, and links to related resources. If you have contributed to open-source TypeScript projects or authored declaration files before, this will take you from ad-hoc definitions to contributions that meet DefinitelyTyped quality expectations. By the end, you'll be comfortable producing, testing, and maintaining types that scale with library changes and consumer usage patterns.
Background & Context
Defining types for third-party JavaScript libraries is important because it helps TypeScript-aware tools provide type checking, autocompletion, and safer integration. Not all libraries ship their own types — that's where DefinitelyTyped comes in. It is a single, community-driven GitHub repository that contains many type packages; each is published to npm under the @types scope.
Contributions go through code review and automated CI checks. DefinitelyTyped has conventions for package layout, test harnesses, and naming. Understanding these rules helps your PRs land faster. Writing maintainable declarations often involves more than one file: you might provide an index.d.ts, supporting types, tests, and package.json metadata. Where libraries use complex dynamic behavior or optional runtime features, writing robust declaration files can be tricky — this is why it's helpful to learn design patterns for declarations and how to verify them with typing tests.
For hands-on guidance about authoring complex declarations, consult our article on Writing Declaration Files for Complex JavaScript Libraries. For one-off or quick fixes, see the guide on Typing Third-Party Libraries Without @types (Manual Declaration Files).
Key Takeaways
- How to locate libraries that need types and the entry points in DefinitelyTyped
- The folder and package conventions used by DefinitelyTyped
- How to author index.d.ts and supporting declarations with module/namespace patterns
- How to write type tests (using dtslint or tsd) to validate declarations
- Local development workflow: building, linting, and running tests before PR
- How to handle module augmentation, overloads, and global vs. module types
- Best practices for cross-version compatibility and maintenance
- Strategies for contributing as part of a monorepo or a maintenance team
Prerequisites & Setup
You should be comfortable with TypeScript types, generics, and modules. Install Node.js and git, and set up a GitHub account. Familiarize yourself with npm publishing basics (even though DefinitelyTyped maintainers publish packages, it's useful to understand the scope mechanism).
Clone the DefinitelyTyped repo, install dependencies, and ensure your local environment can run the tests used by the project. Example quick setup:
git clone https://github.com/DefinitelyTyped/DefinitelyTyped.git cd DefinitelyTyped npm install # If you use yarn: yarn
Additionally, read the CONTRIBUTING.md in the upstream repository and enable a code editor configured for TypeScript so you can iterate quickly. If you work in a monorepo or want to share types across packages, check strategies in Managing Types in a Monorepo with TypeScript.
Main Tutorial Sections
1) Finding and Selecting Packages to Type
Start by identifying packages that lack types or have outdated ones. Search npm and GitHub issues, or use the DefinitelyTyped issue tracker. Prioritize libraries that are widely used and have stable APIs.
Tip: look for author requests in a package's issues or a community signal like many dependents. For smaller internal libs, consider writing a local declaration before contributing upstream. See the guide on Typing Third-Party Libraries Without @types (Manual Declaration Files) for examples of local ad-hoc declarations.
2) Understanding the Library API Surface
Read source code, README usage examples, and existing tests in the target library. Map the public API surface: top-level exports, classes, functions, namespaces, and configuration objects.
Create an outline of types you need (interfaces, enums, function overloads). Drafting this before writing code avoids rework. If the library is JS-based and uses dynamic patterns, add runtime checks or tests to ensure your type assumptions are valid.
3) Creating the Package Folder in DefinitelyTyped
Follow the repository layout: each package lives under the types/<package> folder and includes index.d.ts, package.json, tests folder, and a README if necessary.
Example minimal tree:
types/mylib/
index.d.ts
package.json
ts3.1/ (optional helpers)
test/
index.tsPopulate package.json with the name field @types/mylib, version and types references. See existing packages for examples in the repository.
4) Writing index.d.ts — Basic Patterns
Start with module declarations for the common import styles used by the library. If the library uses commonjs default exports:
// index.d.ts
declare module 'mylib' {
export function doThing(name: string): boolean;
export interface Options { verbose?: boolean }
}
export = require('mylib'); // rarely used in DT, prefer `export as namespace` where global support neededIf the package supports ES Module default exports:
declare module 'mylib' {
export default function doThing(name: string): boolean;
}Make sure you mirror the real runtime semantics. For complex libraries, see patterns in Writing Declaration Files for Complex JavaScript Libraries.
5) Handling Globals and UMD Patterns
Some libraries expose global names in a browser or support UMD (both global and module). Use export as namespace to support global usage and module declarations for bundlers.
Example UMD pattern:
declare namespace MyLib {
function doThing(name: string): boolean;
interface Options { verbose?: boolean }
}
export as namespace MyLib;
export = MyLib;Test both import and global scenarios in your test suite included under test/.
6) Writing Type Tests (dtslint / tsd)
Tests in DefinitelyTyped validate your types against scenarios that consumers may encounter. Historically, dtslint has been used; some projects prefer tsd. In the types repo, create test/index.ts with usage examples and type assertions.
Example:
import mylib = require('mylib');
// should compile
const ok: boolean = mylib.doThing('x');
// @ts-expect-error
mylib.doThing(123);Run npm test in the root or use the repo's recommended commands. If compilation errors are produced, iterate until tests reflect intended usage.
7) Dealing with Overloads, Generics and Conditional Types
Many libraries use overloaded functions or return polymorphic types. Express this with TypeScript overloads and generics. Keep overloads ordered with the most specific first.
Example overloads:
export function request(url: string, callback: (res: string) => void): void; export function request<T>(url: string, opts: RequestOptions): Promise<T>;
When generics touch conditional mapping, prefer simpler patterns if consumers are mostly static — but for advanced APIs, conditional types can provide great ergonomics.
If you struggle expressing behavior, mimic runtime behavior with wrapper types and write tests to cover edge cases.
8) Module Augmentation and Patch Types
If a library augments global objects or plugins extend types (for example, Express middleware adds fields to Request), use module augmentation:
declare global {
namespace Express {
interface Request { user?: { id: string } }
}
}Place augmentation files where the consumer will pick them up (often in index.d.ts or a dedicated augmentation file) and document usage. For practical guidance on middleware typings, see Typing Express.js Middleware: A Practical TypeScript Guide.
9) Local Testing and Linting Workflow
Run local checks before opening a PR. The DefinitelyTyped project has scripts to lint and test types. Typical commands include npm test at the repo root or npm run lint in a package.
Make sure your editor's TypeScript version matches the one used by the CI; use the repo's recommended TypeScript compiler version. For performance and faster iteration when compiling many packages, review techniques in Performance Considerations: TypeScript Compilation Speed.
10) Submitting a Clean Pull Request
Follow the contribution checklist: add a clear PR title, link to the original library, explain assumptions, and include test cases. Keep changes scoped to your package folder. If updating an existing type, explain breaking changes and provide migration tips.
Use concise commits and rebase if necessary. Include maintainers and tag authors if the upstream library has active contributors. Many maintainers appreciate a thorough PR description with code examples showing how types map to runtime behavior.
Advanced Techniques
Once you can produce standard declarations, level up with several patterns: splitting types into version-specific folders (ts3.1, ts4.1) when newer TS features enable better ergonomics; using mapped and conditional types to express complex behaviors; and adding JSDoc with @deprecated tags to guide consumers. For libraries in monorepos or when consuming types inside large apps, coordinate types with workspace tooling and path mappings. See Managing Types in a Monorepo with TypeScript for detailed patterns.
Optimize CI feedback by isolating your package and running only its tests locally. Use tsc --showConfig to debug compiler settings. When performance matters, reduce ambient types or adopt a narrower lib target. For linting consistency and to prevent style churn, integrate lint rules from Integrating ESLint with TypeScript Projects (Specific Rules) in your local workflow and pre-commit hooks.
Best Practices & Common Pitfalls
Do:
- Mirror runtime semantics precisely — incorrect assumptions lead to subtle type errors in consumer code.
- Write tests that simulate common and edge-case usages.
- Keep API shape minimal and explicit; prefer explicit overloads over over-generalized any-based types.
- Document non-obvious behaviors in the package README or type comments.
Don't:
- Rely on excessive type gymnastics unless they significantly improve DX — simpler types are often more maintainable.
- Break consumers with minor changes; consider non-breaking augmentation or major-version bumps if necessary.
- Forget to update tests when the upstream library changes.
Common pitfalls include improper handling of default vs named exports, forgetting UMD/global modes, and not testing asynchronous patterns. Organize your types using the patterns in Best Practices for Structuring Large TypeScript Projects and code organization techniques from Code Organization Patterns for TypeScript Applications to keep your declarations maintainable.
Real-World Applications
Types on DefinitelyTyped empower many practical workflows: editors provide accurate autocompletion, build-time checks catch mistakes earlier, and large codebases can safely upgrade dependencies. For example, enabling types for a logging library reduces misconfigurations, while types for a database client avoid runtime crashes due to wrong query result assumptions — see patterns in Typing Database Client Interactions in TypeScript.
Types also help in cross-team collaboration: when a UI team consumes a third-party date library, precise types reduce onboarding time. For server-side apps using Service Workers or Web Workers, types reduce concurrency bugs; see our guides on Using TypeScript with Service Workers: A Practical Guide and Using TypeScript with Web Workers: A Comprehensive Guide for Intermediate Developers.
Conclusion & Next Steps
Contributing to DefinitelyTyped is an excellent way to sharpen your TypeScript skills while benefiting the community. Start small: fix a type typo or add tests for an existing package. As you gain confidence, tackle more complex libraries and maintenance work. Continue learning by exploring advanced declaration patterns and by reading community guidelines. If you're working across many packages, review monorepo strategies in Managing Types in a Monorepo with TypeScript.
Next steps: pick a package, open an issue describing your plan, and prepare a focused PR with tests and documentation.
Enhanced FAQ
Q1: How do I decide whether to contribute to DefinitelyTyped or ask the library to ship types?
A: Prefer upstream types when library maintainers are active and willing to adopt them; it's better for consumers. However, if the library has slow maintenance or you only need a quick fix, contribute to DefinitelyTyped. Provide a PR and also open an issue suggesting that the library include types; this gives maintainers the option later. If you anticipate keeping definitions up-to-date with frequent library changes, coordinate with maintainers.
Q2: What should I include in the package.json for a DefinitelyTyped package?
A: The types repo expects a narrow package.json that documents the TypeScript versions supported and references to the test harness. Typically it includes name, version, license, main (not required for DT), and types. Check other packages in the repo for templates. Follow the CONTRIBUTING guide in the repo for exact fields required.
Q3: How do I test code that relies on runtime features not visible to TypeScript?
A: Write explicit tests in test/ that mimic runtime usage patterns. If runtime behavior affects types (e.g., a function sometimes returns a Promise), ensure you model the conditional types and provide tests for both branches. If behavior is environment-specific, document that in the types README and use @ts-expect-error for intentional mismatches in tests.
Q4: What’s the best way to represent a function that can be called with either a callback (node-style) or return a Promise?
A: Use overloads where the callback signature appears first, and the Promise-returning overload second. Example:
export function fetch(url: string, callback: (err: Error | null, res?: string) => void): void; export function fetch(url: string): Promise<string>;
Document the runtime conditions that cause either behavior.
Q5: How do I handle types for a package that supports both ESM and CommonJS imports?
A: Provide declarations that support both module systems. Use export = patterns for CommonJS default exports and export default for ESM-friendly shapes. Sometimes you might provide separate entry points or a wrapper that covers both. Include test cases that import using both styles.
Q6: How do I update types when the underlying library introduces breaking changes?
A: Open a PR that updates types and bump the version in the package metadata if needed. Provide migration notes in the PR description and in the types README. If the change is breaking for consumers, coordinate with downstream maintainers. Tests should reflect new behavior.
Q7: What linters and rules should I use when writing declaration files?
A: Follow the repository's eslint and type-checking rules. For project-specific linting, consult Integrating ESLint with TypeScript Projects (Specific Rules) to enforce consistent style. Use the repo's scripts for dtslint and other checks.
Q8: How do I express a complex callback type or an event emitter with many typed events?
A: Use function overloads, mapped types, or generic event maps. Example pattern for typed events:
interface EventMap { 'data': string; 'end': void }
class Emitter<E extends Record<string, any>> {
on<K extends keyof E>(ev: K, handler: (payload: E[K]) => void): this;
}
declare const e: Emitter<EventMap>;
e.on('data', s => console.log(s));This models event names and payloads cleanly and prevents typos.
Q9: Can I include utility types and helper interfaces in index.d.ts?
A: Yes. Add helper types that are exported or kept internal (unexported) if only used by other declarations. Document exported utility types to help consumers. Avoid leaking implementation-only types unless necessary.
Q10: Where can I learn advanced patterns and see real examples?
A: Study high-quality packages in the DefinitelyTyped repo and read targeted guides. For complex declaration authoring, read Writing Declaration Files for Complex JavaScript Libraries. For practical examples of middleware typings consult Typing Express.js Middleware: A Practical TypeScript Guide. If you're concerned about compile performance while iterating, review Performance Considerations: TypeScript Compilation Speed.
