Anatomy of tsconfig.json
A tsconfig.json file marks the root of a TypeScript project and tells the compiler which files to process and how. Run tsc with no input files in a directory that contains one, and the compiler reads every setting from it. The file has a small number of top-level keys — compilerOptions, include, exclude, files, extends, and references — and the vast majority of your configuration lives inside compilerOptions. This page maps out that structure so the rest of this section can dive into individual options.
Generating a baseline config
You rarely write a tsconfig.json from scratch. The compiler can scaffold a documented one for you, which is the fastest way to see what is available.
npx tsc --init
This writes a tsconfig.json with most options listed and commented out, plus a sensible strict baseline enabled. From there you uncomment and tune what you need rather than memorizing every flag.
The top-level shape
A real-world config is usually compact. The keys below are the ones you will touch most often.
{
// Inherit a shared base config
"extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"rootDir": "src",
"outDir": "dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Each key has a distinct job. compilerOptions controls how code is type-checked and emitted, while include/exclude/files control which files are part of the program.
What each top-level key does
The table summarizes the keys you are most likely to use.
| Key | Purpose |
|---|---|
compilerOptions | All compiler behavior: target, module, strictness, output, etc. |
include | Glob patterns for files to compile (defaults to everything under the config) |
exclude | Glob patterns to remove from include (defaults include node_modules) |
files | An explicit, non-glob list of files to compile |
extends | Path or package name of a base config to inherit from |
references | Project references for monorepos and composite builds |
excludeonly filters whatincludealready selected — it does not stop the compiler from pulling in a file that some other included file imports. To truly keep a file out of the program, do not import it.
compilerOptions is the heart
Almost everything interesting happens here. Options fall into rough groups: language/output (target, module, lib), module resolution (moduleResolution, paths, baseUrl), output layout (rootDir, outDir, declaration, sourceMap), and type-checking strictness (strict and its family).
{
"compilerOptions": {
"target": "ES2022", // JS language level to emit
"lib": ["ES2022", "DOM"], // ambient type libraries available
"module": "ESNext", // module format of emitted code
"strict": true, // enable all strict type-checking flags
"noUncheckedIndexedAccess": true // add `undefined` to index access
}
}
The remaining pages in this section unpack these groups in detail. For now, the key mental model is: one object, many independent switches, each documented in the official compiler reference.
Inheriting with extends
The extends key lets a config build on another. The child config is merged on top of the parent, with the child winning on conflicts. This is how teams share a single strict baseline across many packages and how the community @tsconfig/* base configs work.
// tsconfig.base.json (shared)
{
"compilerOptions": {
"strict": true,
"target": "ES2022",
"skipLibCheck": true
}
}
// tsconfig.json (per-package)
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src"]
}
Note that include, exclude, and files are not inherited in older TypeScript versions and must be redeclared; relative paths in the parent are resolved relative to the parent file. Modern TypeScript also accepts an array in extends to merge several bases.
Best Practices
- Generate a starting point with
tsc --init, then trim it to only the options you set intentionally. - Keep a shared
tsconfig.base.jsonand have each packageextendsit to avoid drift. - Always set
include(and an explicitoutDir) rather than relying on implicit whole-directory compilation. - Keep
compilerOptionsgrouped and commented so future readers know why a flag is set. - Enable
strict: truefrom day one — retrofitting it later is far more painful. - Add
node_modulesand your output folder toexcludeto keep editor performance fast. - Prefer
referencesover deep relative imports when a repo grows into multiple build units.