Skip to content
TypeScript ts config 3 min read

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.

KeyPurpose
compilerOptionsAll compiler behavior: target, module, strictness, output, etc.
includeGlob patterns for files to compile (defaults to everything under the config)
excludeGlob patterns to remove from include (defaults include node_modules)
filesAn explicit, non-glob list of files to compile
extendsPath or package name of a base config to inherit from
referencesProject references for monorepos and composite builds

exclude only filters what include already 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.json and have each package extends it to avoid drift.
  • Always set include (and an explicit outDir) rather than relying on implicit whole-directory compilation.
  • Keep compilerOptions grouped and commented so future readers know why a flag is set.
  • Enable strict: true from day one — retrofitting it later is far more painful.
  • Add node_modules and your output folder to exclude to keep editor performance fast.
  • Prefer references over deep relative imports when a repo grows into multiple build units.
Last updated June 29, 2026
Was this helpful?