tsconfig.json is the configuration file of a TypeScript project. When you run tsc with no arguments, the compiler looks for it in the current folder (then in parent folders), reads the options in compilerOptions, and checks the files listed by include. A small but complete one:
{
"compilerOptions": {
"target": "es2022",
"module": "nodenext",
"strict": true,
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src"]
}
This compiles every TypeScript file in src into JavaScript in dist, as ES2022 code, using Node.js's module rules, with all strict checks on. npx tsc --init generates a longer starting file with a comment on every option.
The file is JSON with comments: // comments, /* */ comments and trailing commas are all accepted.
A Recommended Starting Config
For a Node.js application or script:
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"target": "es2024",
"lib": ["es2024"],
"module": "nodenext",
"types": ["node"],
"strict": true,
"noUncheckedIndexedAccess": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"sourceMap": true
},
"include": ["src"]
}
It needs npm install --save-dev @types/node for the Node.js types. With module: "nodenext", the "type" field in package.json decides whether a .ts file is an ES module ("type": "module") or CommonJS ("type": "commonjs", which npm init -y writes). Use "type": "module" for new projects that use import and export.
For code that a bundler such as Vite, esbuild or webpack builds, the bundler writes the JavaScript and tsc only checks:
{
"compilerOptions": {
"target": "es2022",
"lib": ["es2022", "dom"],
"module": "preserve",
"noEmit": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"verbatimModuleSyntax": true,
"isolatedModules": true,
"skipLibCheck": true,
"jsx": "react-jsx"
},
"include": ["src"]
}
Framework starters (Vite, Next.js, Angular) generate their own tsconfig.json. Start from theirs rather than replacing it.
The Options That Matter
| Option | What it does | TypeScript 7 default |
|---|---|---|
target | JavaScript version of the output; newer syntax is rewritten for older targets | es2025 |
lib | Built-in API types the checker knows (Array.prototype.at, Map, document) | Matches target, plus the DOM |
module | Kind of module code emitted and the module rules applied | esnext |
moduleResolution | How import paths are found on disk | nodenext or node16 with the matching module, otherwise bundler |
strict | Turns on the whole family of strict checks | true |
rootDir | Folder whose structure is mirrored into outDir | The folder that holds tsconfig.json |
outDir | Where the .js (and .d.ts) files go | Next to each source file |
include / exclude / files | Which files are part of the project | Every .ts file under the folder |
types | Which @types packages load without an import | [], none |
noEmit | Check only, write nothing | false |
declaration | Also write .d.ts type declaration files | false |
sourceMap | Write .js.map files for debuggers | false |
esModuleInterop | Lets import x from "cjs-package" work with CommonJS packages | true (cannot be turned off) |
skipLibCheck | Skip type-checking .d.ts files, including those in node_modules | false |
target and lib
target says which JavaScript version the output must run on. Syntax newer than the target is rewritten: with "target": "es2017", a class field or ??= is turned into older code. The lowest target TypeScript 7 accepts is es2015 (es6); es5 was removed.
target does not add missing runtime APIs. That is lib's job to describe and a polyfill's job to supply. lib tells the checker which built-in objects and methods exist. Its default follows target and also includes the browser DOM types, so document type-checks even in a Node.js project unless you set lib yourself. Using a method from a newer standard than your lib is a compile error:
src/b.ts(1,24): error TS2550: Property 'toSorted' does not exist on type 'number[]'. Do you need to change your target library? Try changing the 'lib' compiler option to 'es2023' or later.
The runnable examples in these docs use the es2022 lib, so toSorted, Object.groupBy and the new Set methods are not available in them.
module and moduleResolution
module has three sensible values for new code:
nodenext: for code Node.js runs. Each file is ESM or CommonJS according to its extension (.mts,.cts) or the nearestpackage.json"type", exactly as Node decides. Relative imports in ES modules need a file extension, written as.jseven though the source is.ts:import { add } from "./math.js".moduleResolutionfollows automatically.preserve: for code a bundler processes. Imports are left as written, and resolution uses thebundlerrules, which allow extensionless imports.esnext: plain ES module output, withbundlerresolution. It is the default whenmoduleis not set.
A common first error with the tsc --init config comes from npm init -y writing "type": "commonjs":
src/main.ts(1,10): error TS1295: ECMAScript imports and exports cannot be written in a CommonJS file under 'verbatimModuleSyntax'.
The file uses import, but package.json says the project is CommonJS. Change "type" to "module". The modules page covers imports and exports in detail.
strict and the Checking Options
"strict": true turns on a group of checks at once: noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, strictBuiltinIteratorReturn, noImplicitThis and useUnknownInCatchVariables. It is the default in TypeScript 7 and on in every runnable example here. Code written for strict mode narrows before it uses a value that may be missing:
Without an annotation, a parameter's type cannot be inferred, and noImplicitAny reports it:
index.ts(2,17): error TS7006: Parameter 'n' implicitly has an 'any' type.
strict does not include every useful check. Two worth adding are noUncheckedIndexedAccess (an array element or index-signature lookup has type T | undefined) and exactOptionalPropertyTypes (an optional property cannot be explicitly set to undefined); tsc --init turns both on. Style checks such as noUnusedLocals, noImplicitReturns and noFallthroughCasesInSwitch are off by default.
Which Files: include, exclude, rootDir, outDir
Without include or files, the project contains every .ts, .tsx and .d.ts file in the folder and its subfolders. node_modules is always left out. The outDir is left out too, but only while you have not set exclude: a custom exclude list replaces that default, so add the output folder to it. include and exclude take glob patterns:
{
"include": ["src", "scripts/**/*.ts"],
"exclude": ["src/**/*.test.ts"]
}
exclude only filters what include found. A file that another included file imports is still compiled.
outDir is where output goes, and rootDir is the part of the source tree that is mirrored into it: with "rootDir": "./src", src/api/users.ts becomes dist/api/users.js. Set both. rootDir defaults to the folder that holds tsconfig.json, so if you set only outDir while the sources sit in src, TypeScript 7 stops with:
error TS5011: The common source directory of 'tsconfig.json' is './src'. The 'rootDir' setting must be explicitly set to this or another path to adjust your output's file layout.
Output Options: noEmit, declaration, sourceMap
"noEmit": truemakestsca pure type checker. Use it when another tool (a bundler,tsx, Node.js type stripping) produces the JavaScript."declaration": truewrites a.d.tsfile per module, the types without the code. Libraries need it so their users get types.declarationMapadds maps for "go to definition" into the.tssource."sourceMap": truewrites.js.mapfiles so debuggers and stack traces point at the.tslines."noEmitOnError": truewrites nothing while there are type errors. Without it,tscreports the errors and still writes the JavaScript.
types, esModuleInterop and skipLibCheck
types lists the @types packages that are loaded globally, without an import. TypeScript 7's default is an empty list, so after npm install --save-dev @types/node you also add "types": ["node"]; otherwise process and require stay unknown (error TS2591: Cannot find name 'process'). Test runners with global functions, such as Jest's describe, go in the same list.
esModuleInterop makes default imports of CommonJS packages work (import express from "express"). It is always on in TypeScript 7, and setting it to false is an error.
skipLibCheck skips type-checking declaration files. It speeds up builds and avoids errors inside node_modules that you cannot fix, at the cost of not noticing conflicts between two libraries' types. Most projects turn it on.
Sharing Settings with extends
extends loads another config and lets this file override parts of it:
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src"]
}
compilerOptions are merged option by option, while include, exclude and files from the base are replaced, not merged, if this file sets them. Paths in the base file are resolved relative to the base file. extends also accepts a package: the @tsconfig/bases project publishes one per environment, for example npm install --save-dev @tsconfig/node24 and then "extends": "@tsconfig/node24/tsconfig.json".
To see the final settings after all extends and defaults are applied, run:
npx tsc --showConfig
Options Removed in TypeScript 7
TypeScript 6 deprecated these settings and TypeScript 7 removed them. A config that still uses one fails with error TS5108 or TS5102, for example Option 'baseUrl' has been removed. Please remove it from your configuration.
| Removed setting | Use instead |
|---|---|
"target": "es5" | es2015 or later |
"moduleResolution": "node" (node10) or "classic" | nodenext or bundler |
"module": "amd", "umd", "system", "none" | nodenext, esnext or preserve, and a bundler for other formats |
baseUrl | paths entries relative to the tsconfig file |
outFile | A bundler |
downlevelIteration | Nothing: targets from es2015 up support iteration natively |
"esModuleInterop": false, "allowSyntheticDefaultImports": false, "alwaysStrict": false | Remove the line; these are always on |
The TypeScript 7 page lists the other changes in that release, including the new defaults this table does not cover.
Frequently Asked Questions
What is tsconfig.json?
It is the configuration file of a TypeScript project. Its presence marks the folder as the project root, compilerOptions sets how the compiler checks and emits code, and include, exclude or files say which files belong to the project. Running tsc with no arguments reads it.
How do I create a tsconfig.json file?
Run npx tsc --init in the project folder (with TypeScript installed). It writes a tsconfig.json with recommended settings and a comment on each option. You can also write the file by hand; {} is a valid tsconfig that uses every default.
What should target be set to in tsconfig?
The oldest JavaScript version your code has to run on. For current Node.js, es2022 or later is safe; TypeScript 7's default is es2025. target controls which newer syntax gets rewritten for older engines and also picks the default lib, the set of built-in APIs the checker knows about.
What is the difference between module and moduleResolution?
module decides the kind of module code the compiler writes (ES import/export or CommonJS require) and which module rules apply. moduleResolution decides how an import path like "./utils.js" or "lodash" is found on disk. Use nodenext for code that Node.js runs, and module: "preserve" (which implies bundler resolution) for code a bundler processes.
Can tsconfig.json have comments?
Yes. The compiler reads it as JSON with comments: // and /* */ comments and trailing commas are allowed, which is why tsc --init writes a file full of commented-out options. Other tools that parse it as strict JSON can fail on them.