Comparison

jsconfig.json vs tsconfig.json

The same file format with different defaults: what a jsconfig.json turns on by itself, how tsc and editors find each file, and when to use which.

  • Examples checked with TypeScript 5.9

At a glance

Last verified 21 October 2026 with TypeScript 5.9.3: the defaults are pinned in this site's tests, and the editor lookup is read from TypeScript's source.

Compared jsconfig.jsontsconfig.json
allowJs true by defaultfalse by default
checkJs false by defaultfalse by default
noEmit true by defaultfalse: tsc writes .js output unless told not to
skipLibCheck true by defaultfalse by default
maxNodeModuleJsDepth 2: reads JavaScript packages for types0: JavaScript packages are untyped
allowSyntheticDefaultImports true by defaultFollows esModuleInterop and module
Automatic type acquisition in editors On by defaultOff by default
Found by tsc with no -p No: run tsc -p jsconfig.jsonYes
Found by editors Yes, after a tsconfig.json in the same folderYes, first in each folder
Options and extends The same options, schema and extendsThe same options, schema and extends

Same format, different defaults

A jsconfig.json is a tsconfig.json under another name. Every option, the schema and extends work the same way; what changes is a handful of defaults TypeScript applies when the file is named jsconfig.json: allowJs, noEmit, skipLibCheck and allowSyntheticDefaultImports on, and maxNodeModuleJsDepth at 2. Those defaults describe a JavaScript project that is checked and never built, which is why this site uses a jsconfig.json in every setup. This site’s tests read them from TypeScript 5.9 on every run.

checkJs is not among them: a jsconfig.json alone gives editors types for your JavaScript but reports no errors in it. Turn on checkJs, or put // @ts-check at the top of a file.

The default that changes results: packages

maxNodeModuleJsDepth: 2 means that when you import a JavaScript package that ships no types, tsc reads its JavaScript for types, two imports deep. With a tsconfig.json (depth 0) the same import is untyped. The examples show the same import both ways:

main.js Error confirmed · TypeScript 5.9.3 · checked 21 October 2026
import { slugify } from "slugify-lite";

console.log(slugify(42));error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
The jsconfig.json default, 2: the same package read for types.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": [
    "*.js"
  ]
}
main.js Error confirmed · TypeScript 5.9.3 · checked 21 October 2026
import { slugify } from "slugify-lite";error TS7016: Could not find a declaration file for module 'slugify-lite'. '/project/node_modules/slugify-lite/index.js' implicitly has an 'any' type.
  Try `npm i --save-dev @types/slugify-lite` if it exists or add a new declaration (.d.ts) file containing `declare module 'slugify-lite';`

console.log(slugify(42));
maxNodeModuleJsDepth 0: a JavaScript-only package is untyped.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": [],
    "maxNodeModuleJsDepth": 0
  },
  "include": [
    "*.js"
  ]
}

Neither is wrong: reading a package’s JavaScript gives you types inferred from code that was not written to be checked, which can be looser or stricter than you expect. The option’s page covers when to change it.

The default that bites: noEmit

A tsconfig.json without noEmit makes tsc write a .js file for every input file, next to it unless outDir is set. For a JavaScript project that means overwriting your sources is one missing option away (tsc refuses to overwrite an input file, with TS5055, but the error surprises people). A jsconfig.json cannot do that by default.

The default also travels through extends: a config that extends a jsconfig.json inherits its file-name defaults, so a second config that writes .d.ts files has to say "noEmit": false, as in the library setup:

main.js Error confirmed · TypeScript 5.9.3 · checked 21 October 2026
export function double(n) {error TS7006: Parameter 'n' implicitly has an 'any' type.
  return n * 2;
}
A jsconfig.json that extends a shared base: strict comes from the base.
jsconfig.json for this example
{
  "extends": "./jsconfig.base.json",
  "compilerOptions": {
    "noEmit": true
  },
  "include": [
    "*.js"
  ]
}

How each file is found

tsc with no arguments looks for a tsconfig.json in the current folder and its parents; a jsconfig.json has to be named, tsc -p jsconfig.json, which is why every npm script on this site spells it out.

Editors find configs through TypeScript’s language service, which walks up from the file you open and, in each folder, takes a tsconfig.json before a jsconfig.json. A jsconfig.json next to a tsconfig.json is therefore ignored by the editor (TypeScript 5.9’s source; we read it rather than ran an editor). The language service also turns on automatic type acquisition for a jsconfig.json project by default: editors such as VS Code then download @types packages for your dependencies into a cache, which can give the editor types that tsc does not have. If the editor and npm run lint disagree about a package, install its @types package as a development dependency, so both read the same file.

Moving from one to the other

To turn a jsconfig.json into a tsconfig.json, rename it and add the defaults you relied on: "allowJs": true, "noEmit": true, "skipLibCheck": true, and "maxNodeModuleJsDepth": 2 if you import JavaScript-only packages. The other direction is the same list in reverse. The jsconfig generator writes checkJs (which implies allowJs), noEmit and skipLibCheck explicitly, so its output means nearly the same under either name; add maxNodeModuleJsDepth if you rename it and import JavaScript-only packages.

Which to choose

Choose jsconfig.json when…

  • The project is JavaScript checked with JSDoc: its defaults (no output, JavaScript files included, packages read for types) are the ones you would set anyway.
  • You want the file name to tell readers there is no build.

Choose tsconfig.json when…

  • The project has .ts files, or tsc writes output (.js or .d.ts) as part of a build.
  • A tool you use looks only for tsconfig.json, as tsc -b with project references and some bundlers and test runners do.
  • You want plain tsc (no -p) to find the config.

Questions

Can I just rename a jsconfig.json to tsconfig.json?

Yes, if you write down the defaults you were relying on: allowJs, noEmit, skipLibCheck and, if you import JavaScript-only packages, maxNodeModuleJsDepth. Without noEmit, tsc starts writing .js files next to yours.

What if a folder has both files?

tsc uses the one you name with -p. Editors that run TypeScript's language service look for tsconfig.json first in each folder, so in TypeScript 5.9 a jsconfig.json beside a tsconfig.json is ignored by the editor.

Does a config that extends a jsconfig.json keep its defaults?

Yes. The defaults come from the file name of the base, so jsconfig.types.json extending jsconfig.json inherits noEmit: true, which is why a declaration-writing config has to set noEmit: false.

Sources

Last reviewed 21 October 2026.