Publish Type Declarations for a Package
Learn how to publish TypeScript declaration files for your npm package, whether by bundling types with your code or publishing to the @types organization.
To publish type declarations for a package, you give consumers an excellent TypeScript experience without any extra effort on their part. When TypeScript finds a "types" field in your package.json, it reads the declarations automatically, no @types package needed.
There are two main paths: bundling types with your own package (the preferred approach) and contributing to the @types organization via DefinitelyTyped. This article covers both.
Path 1: Bundle Types with Your Package
If you maintain the package and its types together, bundle them in the same npm publication.
Auto-Generating from TypeScript Source
If your package is written in TypeScript, the compiler can produce declaration files automatically. Enable the declaration option in tsconfig.json:
{
"compilerOptions": {
"declaration": true,
"declarationDir": "./dist/types",
"outDir": "./dist"
}
}When you run tsc with that configuration, each source file under src/ produces both a compiled JavaScript file and a matching declaration file, mirrored into the output directories shown below.
src/
index.ts --> dist/index.js + dist/types/index.d.ts
utils.ts --> dist/utils.js + dist/types/utils.d.tsThe declaration files contain only the public types from your source, implementation details are stripped away. This is the standard approach for TypeScript libraries.
Point package.json to the Types
Set the "types" field in package.json to the main declaration file:
{
"name": "my-library",
"version": "1.0.0",
"main": "./dist/index.js",
"types": "./dist/types/index.d.ts"
}The types field tells TypeScript where to find the entry point for your package's type information. When a consumer imports from "my-library", TypeScript reads this file to understand what is exported.
Include .d.ts Files in the Published Tarball
Make sure your declaration files are included in the npm publication. Add them to the files field or ensure they are not excluded by .npmignore:
{
"files": ["dist/"]
}If dist/ contains both compiled JavaScript and a types/ folder with declaration files, this is sufficient. Always run npm pack --dry-run before publishing to verify which files will be included.
Path 2: Publishing to @types via DefinitelyTyped
If you are typing a package you do not own, contribute to DefinitelyTyped. The types will be published to the @types scope on npm automatically.
The contribution workflow follows the same shape for every package:
Fork the repository
Fork the DefinitelyTyped repository on GitHub.
Create a package folder
Create a folder under types/your-package-name/.
Write the declaration file
Write index.d.ts with your type definitions.
Write a test file
Add a test file that exercises the API.
Add package metadata
Include a package.json with metadata and a minimal tsconfig.json.
Submit a pull request
Open a pull request against the main repository.
After review and merge, the types-publisher bot publishes your types to npm as @types/your-package-name. Consumers install them with npm install --save-dev @types/your-package-name.
For more on the DefinitelyTyped ecosystem, see DefinitelyTyped explained for TypeScript.
Handling Dependencies
If your declaration file imports types from another package, you must declare that package as a dependency -- not a devDependency. Your consumers need those types too.
{
"name": "my-express-plugin",
"types": "./index.d.ts",
"dependencies": {
"@types/express": "^5.0.0"
}
}A consumer who installs my-express-plugin will automatically get @types/express as well, because npm installs transitive dependencies.
If your package only uses a type internally and never exposes it to consumers, you can use devDependencies instead. But when in doubt, use dependencies, it is safer for the consumer.
Versioning Types with typesVersions
Sometimes you need to ship different type definitions for different versions of TypeScript. The typesVersions field in package.json handles this.
{
"name": "my-library",
"types": "./index.d.ts",
"typesVersions": {
">=5.0": { "*": ["ts5.0/*"] },
">=4.0": { "*": ["ts4.0/*"] }
}
}With this configuration:
- TypeScript 5.0 and above resolves imports to the ts5.0/ folder.
- TypeScript 4.0 to 4.9 resolves to the ts4.0/ folder.
- Older versions fall back to the types field.
This is useful when newer TypeScript features let you write cleaner types, but you want to maintain compatibility with older compiler versions. The feature uses Node.js semver ranges to match the TypeScript version.
File-Level Redirects
You can also redirect individual files instead of entire folders:
{
"typesVersions": {
"<5.0": { "index.d.ts": ["index.v4.d.ts"] }
}
}On TypeScript 5.0+, importing "my-library" resolves to index.d.ts. On older versions, it resolves to index.v4.d.ts. Use this sparingly, folder-level redirects are usually cleaner.
Red Flags to Avoid When Publishing
Several practices cause problems for consumers. Avoid them in published declaration files.
Do Not Use /// <reference path="..." />
The reference path directive creates fragile, file-path-dependent relationships. Use reference types instead:
// Wrong
/// <reference path="../typescript/lib/typescriptServices.d.ts" />
// Correct
/// <reference types="typescript" />The types form references a package by name, not a file by path. It is portable across projects and environments.
Do Not Copy Other Packages' Types
If your types depend on types from another package, declare that package as a dependency, do not copy its declaration files into your own package. Copying types creates duplication, drift, and versioning problems.
Do Not Forget to Test
Before publishing, verify that your types work by writing a test file that imports and uses your package:
// test.ts -- compile this, do not run it
import { myFunction, MyType } from "my-library";
const result: MyType = myFunction("test");
// If this compiles, your types are working.A test file that compiles successfully is the simplest and most effective validation.
For a comprehensive list of pitfalls, see common declaration file mistakes.
Quick Checklist Before Publishing
Before you run npm publish, confirm:
- The types field (or typings) points to the correct entry declaration file.
- Declaration files are included in the published tarball.
- Dependencies on @types packages are in dependencies, not devDependencies.
- No reference path directives remain.
- No other packages' types are copied into your package.
- A test file compiles successfully against your types.
- npm pack --dry-run shows the expected file list.
If all items pass, your package is ready for TypeScript consumers.
Rune AI
Key Insights
- Set the types field in package.json to point to your main declaration file.
- Enable declaration: true in tsconfig.json to auto-generate .d.ts files from TypeScript source.
- Include .d.ts files in your published tarball so consumers can access them.
- Use typesVersions to ship different type definitions for different TypeScript versions.
- Always mark @types dependencies as regular dependencies, not devDependencies, if consumers need them.
Frequently Asked Questions
Should I publish my types with my package or to DefinitelyTyped?
What is the difference between the types and typings fields?
Do I need to include .d.ts files in my published package?
Conclusion
Publishing type declarations makes your package a first-class citizen in the TypeScript ecosystem. Whether you bundle types from TypeScript source, write them by hand, or contribute to DefinitelyTyped, the goal is the same: consumers get autocomplete, type checking, and a better development experience without any extra steps.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.