Skip to content

/commands/typescript/generate-declaration-files

tsc --declaration --emitDeclarationOnly: generate .d.ts files

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash) · v24.20.0updated
tool
TypeScript
versions
TypeScript 2.8+ (emitDeclarationOnly), TypeScript 7.0
os
any

npx tsc --declaration --emitDeclarationOnly writes only the .d.ts type declaration files for your TypeScript sources, without any JavaScript. Use it when another tool (esbuild, swc, Babel, a bundler) produces the .js files for a library and you still need the types that consumers' editors and compilers read. Add --outDir to choose where they go.

Generate declarations

bash
npm init -y >/dev/null
npm install --silent --no-audit --no-fund --save-dev typescript@7.0.2 >/dev/null 2>&1
mkdir src
cat > src/index.ts <<'EOF'
export interface Options { retries?: number }
/** Adds two numbers. */
export function add(a: number, b: number): number { return a + b; }
export const version = "1.0.0";
export async function fetchWithRetry(url: string, opts: Options = {}) {
  return { url, retries: opts.retries ?? 3 };
}
EOF
npx tsc --declaration --emitDeclarationOnly --target es2022 --outDir types src/index.ts
find types src -type f | sort
cat types/index.d.ts

Output

src/index.ts
types/index.d.ts
export interface Options {
    retries?: number;
}
/** Adds two numbers. */
export declare function add(a: number, b: number): number;
export declare const version = "1.0.0";
export declare function fetchWithRetry(url: string, opts?: Options): Promise<{
    url: string;
    retries: number;
}>;

Only types/index.d.ts is written: the function bodies are gone, the inferred return type of fetchWithRetry is spelled out, and the JSDoc comment is kept for editor tooltips. The folder has no tsconfig.json, so naming the file on the command line is fine here; in a real project the options live in tsconfig.json (see below).

In tsconfig.json

tsconfig.json
{
  "compilerOptions": {
    "declaration": true,
    "emitDeclarationOnly": true,
    "declarationMap": true,
    "outDir": "dist"
  },
  "include": ["src"]
}

declarationMap adds .d.ts.map files, so "Go to definition" in an editor jumps to the .ts source instead of the declaration.

OptionEffect
declarationEmit a .d.ts file for every .ts or .js input (required for emitDeclarationOnly)
emitDeclarationOnlyWrite the .d.ts files and no JavaScript
declarationDirPut declarations in a different folder from outDir
declarationMapEmit .d.ts.map files that map declarations back to sources
allowJs + declarationGenerate declarations for JavaScript files from JSDoc types
isolatedDeclarationsRequire explicit types on exports so other tools can emit .d.ts files without type checking (TypeScript 5.5+)

Pitfalls

  • tsc src/index.ts with a tsconfig.json present fails with error TS5112 on TypeScript 6.0+; run npx tsc (or -p tsconfig.build.json) without file names, or pass --ignoreConfig.
  • noEmit wins. With "noEmit": true in the config, nothing is written; use a separate build config without it.
  • Types of dependencies. If your exported types mention a package's types, consumers need that package (or its @types package) too, or they get TS2307 Cannot find module.
  • package.json. Declarations are only found when types or the types condition in exports points at them.

Confirm it worked

List the output folder: there must be one .d.ts per source file and no .js files. Then import the package from a test project and hover a function in the editor to see the declared types.

FAQ

Can tsc generate .d.ts files for a JavaScript project?

Yes. Add allowJs and declaration (with emitDeclarationOnly) and tsc reads JSDoc comments to infer the types. The TypeScript handbook page on creating .d.ts files from .js files describes the setup.

How do consumers find the generated declarations?

Through the types field, or a types condition in exports, in package.json. Point it at the .d.ts file that matches the JavaScript entry point, for example dist/index.d.ts.

Why does tsc emit nothing with emitDeclarationOnly?

emitDeclarationOnly needs declaration (or composite) to be on; otherwise tsc reports an error and writes nothing. noEmit in tsconfig.json also wins over it, so remove noEmit.

Sources