Recipe · Modules and packages

How do I add types for an npm package that has none?

  • Node.js (ES modules)
  • declare module
  • .d.ts
  • TS7016
  • Checked with TypeScript 5.9

The problem I import a small npm package that has no types and no @types package. tsc says it could not find a declaration file. How do I give it types from my own project?

The short answer

Check npm for @types/<name> first. If there is none, write a declaration file in your project with declare module "<name>", describe the exports you use, and include the file in jsconfig.json:

types/tiny-csv.d.ts TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
declare module "tiny-csv" {
  export interface ParseOptions {
    /** The field separator; "," when left out. */
    delimiter?: string;
  }

  export function parse(text: string, options?: ParseOptions): string[][];
}
A local declaration file for a package that ships no types.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": [],
    "maxNodeModuleJsDepth": 0
  },
  "include": ["*.js", "types/*.d.ts"]
}

The JavaScript that imports the package is then checked against those declarations:

app.js TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
import { parse } from "tiny-csv";

const rows = parse("sku;quantity\nLAMP-01;4", { delimiter: ";" });
for (const [sku, quantity] of rows) {
  console.log(sku.toLowerCase(), Number(quantity));
}
A local declaration file for a package that ships no types.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": [],
    "maxNodeModuleJsDepth": 0
  },
  "include": ["*.js", "types/*.d.ts"]
}

Why it works

declare module "tiny-csv" { … } tells tsc what the module named tiny-csv exports, wherever the import is. Declaration files hold types only, so the syntax inside is TypeScript’s (export function parse(…): string[][];), even in a JavaScript project. tsc finds the file because include lists types/*.d.ts; it does not have to sit next to the package.

The same thing in TypeScript

A TypeScript project uses the same .d.ts file in the same way; declaration files are the one place where JavaScript and TypeScript projects write identical code. That is why this example has no TypeScript tab.

What goes wrong

Without a declaration, and with maxNodeModuleJsDepth at 0, the import is TS7016 and the package is any:

app.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: app.js(2,23): error TS7016: Could not find a declaration file for module 'tiny-csv'. '/project/node_modules/tiny-csv/index.js' implicitly has an 'any' type. Try `npm i --save-dev @types/tiny-csv` if it exists or add a new declaration (.d.ts) file containing `declare module 'tiny-csv';` 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. app.js(2,23): error TS7016: Could not find a declaration file for module 'tiny-csv'. '/project/node_modules/tiny-csv/index.js' implicitly has an 'any' type. Try `npm i --save-dev @types/tiny-csv` if it exists or add a new declaration (.d.ts) file containing `declare module 'tiny-csv';` 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. app.js(2,23): error TS7016: Could not find a declaration file for module 'tiny-csv'. '/project/node_modules/tiny-csv/index.js' implicitly has an 'any' type. Try `npm i --save-dev @types/tiny-csv` if it exists or add a new declaration (.d.ts) file containing `declare module 'tiny-csv';` checked 21 October 2026
import { parse } from "tiny-csv";error TS7016: Could not find a declaration file for module 'tiny-csv'. '/project/node_modules/tiny-csv/index.js' implicitly has an 'any' type.
  Try `npm i --save-dev @types/tiny-csv` if it exists or add a new declaration (.d.ts) file containing `declare module 'tiny-csv';`

console.log(parse("sku,quantity"));
The same import with no declaration file.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": [],
    "maxNodeModuleJsDepth": 0
  },
  "include": ["*.js"]
}

With the declaration, calls are checked: an option the declaration does not list fails (TS2353):

app.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: app.js(4,25): error TS2353: Object literal may only specify known properties, and 'separator' does not exist in type 'ParseOptions'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. app.js(4,25): error TS2353: Object literal may only specify known properties, and 'separator' does not exist in type 'ParseOptions'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. app.js(4,25): error TS2353: Object literal may only specify known properties, and 'separator' does not exist in type 'ParseOptions'. checked 21 October 2026
import { parse } from "tiny-csv";

parse("sku;quantity", { separator: ";" });error TS2353: Object literal may only specify known properties, and 'separator' does not exist in type 'ParseOptions'.
With the declaration, a wrong option is caught.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": [],
    "maxNodeModuleJsDepth": 0
  },
  "include": ["*.js", "types/*.d.ts"]
}

Gotchas

  • declare module "tiny-csv"; alone silences TS7016. That one-line form, which tsc’s own message suggests, types the whole module as any; use it only as a stopgap.
  • The .d.ts must not have top-level imports or exports. A top-level import turns the file into a module, and its declare module becomes an augmentation of an existing module instead of a new declaration: the import is TS7016 again.
  • skipLibCheck skips your file too. With skipLibCheck on (the jsconfig.json default), a typo inside the .d.ts itself goes unreported; the calls against it are still checked.

Questions

Why do I not get TS7016 in my project?

A jsconfig.json sets maxNodeModuleJsDepth to 2, so tsc reads the package's JavaScript and infers what it can instead of reporting TS7016. The inferred types are often loose (parameters become any); a declaration file replaces them. The examples here set the option to 0, the tsconfig.json default, to show the plain case.

Should I publish my declarations?

If they are good, consider sending them to DefinitelyTyped as @types/<name>, or to the package itself. Until then, a file in your repository works the same way.

Sources

Last reviewed 21 October 2026.