Since TypeScript 3.7, tsc can write declaration (.d.ts) files from JavaScript. Turn on declaration to write them, emitDeclarationOnly so that tsc writes only those and never a .js file, and declarationDir to put them in a folder of their own.
The .d.ts files carry your JSDoc types in TypeScript syntax. Publish them with your package, point package.json at them, and anyone who installs the package gets your types in their editor and their tsc, whether they write TypeScript or checked JavaScript.
At a glance
| Write | In a second config used for publishing: "declaration": true with "emitDeclarationOnly": true and a declarationDir |
|---|---|
| On the command line | --declaration |
| Type | boolean |
| Default in TypeScript 5.9 | false (on when composite is on) |
| TypeScript's description | Generate .d.ts files from TypeScript and JavaScript files in your project. |
The description is quoted from TypeScript's own option list (Apache-2.0); see the notices.
Examples
/**
* @typedef {object} Point
* @property {number} x
* @property {number} y
*/
/**
* The distance between two points.
* @param {Point} a
* @param {Point} b
*/
export function distance(a, b) {
return Math.hypot(a.x - b.x, a.y - b.y);
}
export const origin = { x: 0, y: 0 };
export class Vector {
/**
* @param {number} x
* @param {number} y
*/
constructor(x, y) {
this.x = x;
this.y = y;
}
length() {
return Math.hypot(this.x, this.y);
}
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"module": "nodenext",
"target": "es2022",
"types": []
},
"include": [
"src/**/*.js"
]
} /**
* @typedef {object} Point
* @property {number} x
* @property {number} y
*/
/**
* The distance between two points.
* @param {Point} a
* @param {Point} b
*/
export function distance(a: Point, b: Point): number;
export namespace origin {
let x: number;
let y: number;
}
export class Vector {
/**
* @param {number} x
* @param {number} y
*/
constructor(x: number, y: number);
x: number;
y: number;
length(): number;
}
export type Point = {
x: number;
y: number;
};jsconfig.types.json, the config that writes it
{
"extends": "./jsconfig.json",
"compilerOptions": {
"noEmit": false,
"declaration": true,
"emitDeclarationOnly": true,
"declarationDir": "types"
}
} A second config for the .d.ts files
Your jsconfig.json keeps noEmit and stays the file your editor and npm run lint use. A second file extends it and turns writing on, for declarations only. The second example above is the file tsc -p jsconfig.types.json wrote from the first; this site runs that config again on every build, in memory, and the build fails if the output no longer matches the file shown.
{
"extends": "./jsconfig.json",
"compilerOptions": {
"noEmit": false,
"declaration": true,
"emitDeclarationOnly": true,
"declarationDir": "types"
}
} npx tsc -p jsconfig.types.json What tsc writes
Compare the two examples:
- a
@typedefbecomes an exportedtype, so users of the package can importPointby name - parameter and return types come from the JSDoc, or from inference where there is none (
distancereturnsnumberwithout a@returnstag) - class properties assigned in the constructor become declared properties
- an exported object literal (
origin) is written as anamespacewith one variable per property, which works the same for importers - the JSDoc comments are copied into the
.d.ts, so hovers in the user's editor show your descriptions
declarationDir and declarationMap
declarationDir sends the .d.ts files to their own folder (here types/), which keeps them apart from your sources and easy to leave out of git. declarationMap also writes a .d.ts.map beside each file; with it, an editor's "go to definition" in a project that uses your package can land in your .js source instead of the .d.ts, provided the source files are published too.
Pointing package.json at the types
TypeScript finds a package's types through its package.json. With an exports field, put a types condition first in each entry; TypeScript's 4.7 release notes say it should always come first.
{
"type": "module",
"exports": {
".": {
"types": "./types/geometry.d.ts",
"default": "./src/geometry.js"
}
},
"files": ["src", "types"]
} Under TypeScript 6.0 and 7.0
Every example on this page also runs under TypeScript 6.0.3 and 7.0.2 when this site is built. One of them reports something different from 5.9.3, the version the page describes; the ✗ on its badge links to its entry below.
rootDir defaults to the config's folder
The pattern: A package that writes .d.ts files from src/ with declarationDir and no rootDir.
Why: Only the 6.0 default for rootDir causes this. 5.9 took the folder that holds every source file (src/), so src/price.js became types/price.d.ts. From 6.0 the default is the folder of the config, so the same file becomes types/src/price.d.ts, and tsc reports TS5011 asking for an explicit rootDir. The .d.ts that 6.0.3 writes is otherwise the same file. Source: TypeScript 6.0 release notes: rootDir now defaults to ..
A small JavaScript module with JSDoc, ready to publish src/geometry.js
- TypeScript 5.9.3
No errors.
Writes the .d.ts files the example shows.
- TypeScript 6.0.3differs from 5.9.3
error TS5011: The common source directory of 'jsconfig.types.json' is './src'. The 'rootDir' setting must be explicitly set to this or another path to adjust your output's file layout. Visit https://aka.ms/ts6 for migration information.
The file it writes:
types/src/geometry.d.ts/** * @typedef {object} Point * @property {number} x * @property {number} y */ /** * The distance between two points. * @param {Point} a * @param {Point} b */ export function distance(a: Point, b: Point): number; export namespace origin { let x: number; let y: number; } export class Vector { /** * @param {number} x * @param {number} y */ constructor(x: number, y: number); x: number; y: number; length(): number; } export type Point = { x: number; y: number; };- TypeScript 7.0.2differs from 5.9.3
jsconfig.types.json(7,5): error TS5011: The common source directory of 'jsconfig.types.json' is './src'. The 'rootDir' setting must be explicitly set to this or another path to adjust your output's file layout. Visit https://aka.ms/ts6 for migration information.
The file it writes:
types/src/geometry.d.ts/** * @typedef {object} Point * @property {number} x * @property {number} y */ export type Point = { x: number; y: number; }; /** * The distance between two points. * @param {Point} a * @param {Point} b */ export declare function distance(a: Point, b: Point): number; export declare const origin: { x: number; y: number; }; export declare class Vector { x: number; y: number; /** * @param {number} x * @param {number} y */ constructor(x: number, y: number); length(): number; }
What works in every version: Set "rootDir": "src" in jsconfig.types.json, which gives the 5.9 layout in every version.
7.0 writes different .d.ts files from JavaScript
The pattern: A package that writes its .d.ts files from JSDoc with declaration and emitDeclarationOnly.
Why: The port rewrote declaration output for JavaScript, and its list of changes says matching the old output exactly is not a goal. With rootDir set to src, TypeScript 7.0.2 still writes different files from 5.9's: a @typedef becomes an export type where its comment is (5.9 puts it last), functions and classes are export declare, an exported object is a const rather than a namespace, and optional properties lose their | undefined. Source: CHANGES.md: Declaration Emit.
A small JavaScript module with JSDoc, ready to publish src/geometry.js
- TypeScript 5.9.3
No errors.
Writes the .d.ts files the example shows.
- TypeScript 6.0.3differs from 5.9.3
error TS5011: The common source directory of 'jsconfig.types.json' is './src'. The 'rootDir' setting must be explicitly set to this or another path to adjust your output's file layout. Visit https://aka.ms/ts6 for migration information.
The file it writes:
types/src/geometry.d.ts/** * @typedef {object} Point * @property {number} x * @property {number} y */ /** * The distance between two points. * @param {Point} a * @param {Point} b */ export function distance(a: Point, b: Point): number; export namespace origin { let x: number; let y: number; } export class Vector { /** * @param {number} x * @param {number} y */ constructor(x: number, y: number); x: number; y: number; length(): number; } export type Point = { x: number; y: number; };- TypeScript 7.0.2differs from 5.9.3
jsconfig.types.json(7,5): error TS5011: The common source directory of 'jsconfig.types.json' is './src'. The 'rootDir' setting must be explicitly set to this or another path to adjust your output's file layout. Visit https://aka.ms/ts6 for migration information.
The file it writes:
types/src/geometry.d.ts/** * @typedef {object} Point * @property {number} x * @property {number} y */ export type Point = { x: number; y: number; }; /** * The distance between two points. * @param {Point} a * @param {Point} b */ export declare function distance(a: Point, b: Point): number; export declare const origin: { x: number; y: number; }; export declare class Vector { x: number; y: number; /** * @param {number} x * @param {number} y */ constructor(x: number, y: number); length(): number; }
What works in every version: Compare the .d.ts files before releasing with 7.0, and commit the new ones once they read right.
Gotchas
- A second config that
extendsajsconfig.jsoninheritsnoEmit: trueeven though the base file never writes it, andnoEmitquietly wins: nothing is written and nothing is reported. Set"noEmit": falseexplicitly. emitDeclarationOnlyneedsdeclaration(orcomposite) on; on its own it is a config error.- Types you never wrote down can surprise you: an untyped parameter becomes
anyin the.d.ts. Check the output before you publish it.
Questions
Should the .d.ts files be committed to git?
Either works. Many packages write them just before publishing (an npm prepack script that runs tsc -p jsconfig.types.json) and keep the folder out of git, so the files can never be stale.
Sources
- TSConfig Reference: declaration · TypeScript
- TSConfig Reference: emitDeclarationOnly · TypeScript
- TSConfig Reference: declarationDir · TypeScript
- TSConfig Reference: declarationMap · TypeScript
- TypeScript 3.7 release notes · TypeScript handbook
- TypeScript 2.8 release notes · TypeScript handbook
- TypeScript 4.7 release notes · TypeScript handbook
- Creating .d.ts Files from .js files · TypeScript handbook
Last reviewed 20 October 2026.