Skip to content
TypeScript ts getting-started 3 min read

tsconfig.json Overview

tsconfig.json is the control center for a TypeScript project. Its presence marks the project root and tells tsc (and your editor) which files to include, how strict to be, what JavaScript to emit, and where to put it. You do not need to memorize every option — a small core set drives most behavior — but understanding the structure pays off because every project, framework template, and CI build revolves around this file. This page is a tour of the shape of tsconfig.json and the options worth knowing early; later pages in the config section go deep on each.

Generating and structuring the file

Create a starter file with tsc --init. The configuration is organized into a top-level object with compilerOptions plus file-selection fields.

npx tsc --init
// tsconfig.json
{
  "compilerOptions": {
    // how the compiler behaves
  },
  "include": ["src"],       // which files to compile
  "exclude": ["node_modules", "dist"]
}

compilerOptions holds the bulk of the settings, while include/exclude/files decide which source files the project contains.

The options you set first

A handful of compilerOptions shape almost everything. target and module control the emitted JavaScript; outDir/rootDir control file layout; strict controls type safety.

{
  "compilerOptions": {
    "target": "es2022",          // JS language version of the output
    "module": "esnext",          // module system of the output
    "moduleResolution": "bundler",
    "outDir": "dist",            // where compiled JS goes
    "rootDir": "src",            // root of your source files
    "strict": true               // enable all strict checks
  }
}

Setting strict: true is the single most impactful choice — it switches on a whole family of checks, including strictNullChecks and noImplicitAny, described below.

What strict turns on

strict is an umbrella flag. Rather than enabling each safety check individually, it activates the recommended set in one line, and new strict checks are added under it in future versions.

Sub-optionEffect when on
strictNullChecksnull/undefined are not silently assignable
noImplicitAnyUntyped values must be annotated, not silently any
strictFunctionTypesFunction parameters are checked contravariantly
strictBindCallApplybind/call/apply are type-checked
strictPropertyInitializationClass fields must be initialized
useUnknownInCatchVariablescatch (e) gives unknown, not any
// With strict on:
function len(s: string | null) {
  return s.length; // ❌ Error: 's' is possibly 'null'.
}

Always start new projects with strict: true. Retrofitting strictness onto a large lax codebase is painful; beginning strict and staying strict is nearly free.

Controlling input and output

include and exclude use glob patterns to define the project’s file set, while outDir and rootDir keep your compiled output cleanly separated from source.

{
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "dist"
  },
  "include": ["src/**/*.ts"],
  "exclude": ["**/*.test.ts", "node_modules"]
}

With this layout, src/app.ts compiles to dist/app.tsdist/app.js, mirroring the directory structure under dist.

Other commonly useful options

Beyond the essentials, a few options improve interop and library output. esModuleInterop smooths CommonJS/ESM imports, declaration emits .d.ts files for libraries, and skipLibCheck speeds up builds by trusting dependency types.

{
  "compilerOptions": {
    "esModuleInterop": true,     // friendlier default imports
    "declaration": true,         // emit .d.ts files (for libraries)
    "sourceMap": true,           // emit source maps for debugging
    "skipLibCheck": true,        // don't re-check node_modules types
    "forceConsistentCasingInFileNames": true
  }
}

These rarely cause problems and are common in framework starter configs.

Extending shared configs

You do not have to hand-write everything. Community base configs (such as @tsconfig/* packages or framework presets) encode good defaults, and you layer your changes on top with extends.

{
  "extends": "@tsconfig/node22/tsconfig.json",
  "compilerOptions": {
    "outDir": "dist"
  },
  "include": ["src"]
}

extends merges the base configuration first, then applies your overrides — a clean way to keep config DRY across a monorepo.

Best Practices

  • Generate the file with tsc --init and trim it to the options you actually use.
  • Always enable strict: true on new projects to get the full safety suite.
  • Set rootDir and outDir to keep compiled output separate from source.
  • Use include/exclude globs to scope the project precisely (exclude tests from builds).
  • Turn on skipLibCheck to speed up builds and esModuleInterop for smoother imports.
  • Extend a maintained base config with extends instead of copying settings everywhere.
Last updated June 29, 2026
Was this helpful?