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.

7 min read

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:

jsonjson
{
  "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.

texttext
src/
  index.ts         -->  dist/index.js  +  dist/types/index.d.ts
  utils.ts         -->  dist/utils.js  +  dist/types/utils.d.ts

The 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:

jsonjson
{
  "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:

jsonjson
{
  "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.

jsonjson
{
  "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.

jsonjson
{
  "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:

jsonjson
{
  "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:

typescripttypescript
// 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:

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

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.
RunePowered by Rune AI

Frequently Asked Questions

Should I publish my types with my package or to DefinitelyTyped?

If your package is written in TypeScript or you maintain the types alongside the source code, bundle them with your package. If you are a third party typing someone else's library, submit to DefinitelyTyped so they are published to the @types scope.

What is the difference between the types and typings fields?

They are synonymous. Both point TypeScript to the main declaration file for your package. types is the modern, preferred name. typings exists for backward compatibility.

Do I need to include .d.ts files in my published package?

Yes. The .d.ts files must be included in the files or not excluded by .npmignore so they are part of the published tarball. Consumers' TypeScript compiler reads them directly from node_modules.

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.