declaration
Emits .d.ts files describing your public API so consumers of the package get types.
"declaration": boolean // default: true if composite, otherwise false- Added in TypeScript 1.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$declarationoptional | boolean= true if composite is enabled, otherwise false | Generate a .d.ts type definition file for every TypeScript or JavaScript file in the project. |
$declarationDiroptional | string= alongside the .js output | Root directory the .d.ts files are written to, instead of sitting beside the emitted JavaScript. Added in TypeScript 2.0. |
$declarationMapoptional | boolean= false | Also emit .d.ts.map files so editors can jump from a declaration back to the original .ts source. Added in TypeScript 2.9. |
$emitDeclarationOnlyoptional | boolean= false | Emit only the .d.ts files and no .js at all. Added in TypeScript 2.8. |
Return value
booleanA .d.ts file emitted next to each .js output (or under declarationDir), describing the module's external API.
Errors
Errors such as TS4023 when an exported value's type comes from a module that cannot be named in the output, and TS4053 when an inferred return type references a private or non-exported name.
Changelog
- PHP 2.0declarationDir added, letting declarations be written to their own tree.
- PHP 2.8emitDeclarationOnly added for projects that transpile JavaScript with another tool.
- PHP 2.9declarationMap added; strongly recommended with project references so Go to Definition lands on source.
- PHP 3.0composite implies declaration, so referenced projects always emit declarations.
- PHP 5.5isolatedDeclarations added; it requires declaration or composite and reports code whose declaration output cannot be produced from one file alone.
declaration makes tsc write a .d.ts beside each JavaScript output. That file is the contract your package ships: it is what package.json's types field points at, and what an editor reads when someone imports your library. Without it, consumers get any or a red squiggle.
The three companions
declarationMap emits .d.ts.map so Go to Definition jumps into your original .ts rather than the generated declaration — turn it on for anything with project references. emitDeclarationOnly emits types and no JavaScript, which is the standard split when esbuild, swc or Vite produces the runtime bundle. declarationDir puts the declarations in their own tree.
What trips people up
Turning declaration on surfaces a class of error that never appears during normal checking. The compiler has to write down every exported type, and it can only write names that are reachable from the output file. An exported function whose inferred return type mentions an un-exported interface, or a type pulled from a devDependency, produces TS4023 or TS4053. The fix is almost always to export the interface or to annotate the return type explicitly instead of relying on inference.
With allowJs, set emitDeclarationOnly or a distinct outDir so generated .js never overwrites your source.
Example
// src/money.ts — compiled with { "declaration": true, "outDir": "dist" }
export interface Money {
currency: "EUR" | "USD";
cents: number;
}
export function format(m: Money): string {
const major = (m.cents / 100).toFixed(2);
return m.currency === "EUR" ? `${major} EUR` : `$${major}`;
}
// dist/money.d.ts then contains exactly:
// export interface Money { currency: "EUR" | "USD"; cents: number; }
// export declare function format(m: Money): string;
console.log(format({ currency: "EUR", cents: 1999 }));
console.log(format({ currency: "USD", cents: 500 }));When to use it
- Publishing an npm package whose types resolve without a separate @types entry.
- Pairing emitDeclarationOnly with esbuild or swc so tsc only produces types.
- Adding declarationMap so Go to Definition across a monorepo lands on source, not on generated .d.ts.
- Satisfying the composite requirement that every referenced project emits declarations.
Frequently asked questions
How do I generate .d.ts files for an npm package?
Set "declaration": true in compilerOptions and point package.json's "types" at the emitted entry file, for example "types": "dist/index.d.ts". Add "declarationMap": true if you also ship source, so editors can navigate into it. If another tool builds your JavaScript, add "emitDeclarationOnly": true so tsc produces only the type files.
Why does my build fail with TS4023 only after enabling declaration?
Because declaration forces the compiler to write every exported type into a file, and it can only write names the output file can reach. TS4023 means an exported value's type comes from a module that is not imported or re-exported in a nameable way. Export the interface, or annotate the value or return type explicitly instead of letting inference reach into a private module.
What does declarationMap actually do?
It emits .d.ts.map files that map each declaration back to the original .ts line. In editors, Go to Definition on a symbol from another project then lands in the real source instead of the generated declaration, and rename works across the project boundary. It is strongly recommended whenever you use project references.
More examples
Types from tsc, JavaScript from a bundler
tsc writes dist/*.d.ts and dist/*.d.ts.map and no JavaScript at all; esbuild or swc emits dist/index.js separately. This is the usual library setup because the bundler is an order of magnitude faster at transpiling, while only tsc can produce correct declarations.
{
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": true,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src/**/*"]
}The error declaration adds, and the fix
The first form checks fine with declaration off. With it on, the compiler must serialise open's return type into the .d.ts and cannot name it. Exporting Session2 and annotating the return gives it a nameable type, and the declaration emits as `export declare function open2(userId: string): Session2;`. The logged value shows the object shape the declaration promises.
// Before: fails with TS4053 once declaration is on.
interface Session {
userId: string;
expiresAt: number;
}
export function open(userId: string) {
return { userId, expiresAt: 1735689600000 };
}
// The inferred return type is an anonymous object that references nothing
// exported, so the emitted .d.ts has no name to write.
// After: export the type and annotate the return.
export interface Session2 {
userId: string;
expiresAt: number;
}
export function open2(userId: string): Session2 {
return { userId, expiresAt: 1735689600000 };
}
console.log(open2("u_42"));
Discussion