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-option | Effect when on |
|---|---|
strictNullChecks | null/undefined are not silently assignable |
noImplicitAny | Untyped values must be annotated, not silently any |
strictFunctionTypes | Function parameters are checked contravariantly |
strictBindCallApply | bind/call/apply are type-checked |
strictPropertyInitialization | Class fields must be initialized |
useUnknownInCatchVariables | catch (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.ts → dist/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 --initand trim it to the options you actually use. - Always enable
strict: trueon new projects to get the full safety suite. - Set
rootDirandoutDirto keep compiled output separate from source. - Use
include/excludeglobs to scope the project precisely (exclude tests from builds). - Turn on
skipLibCheckto speed up builds andesModuleInteropfor smoother imports. - Extend a maintained base config with
extendsinstead of copying settings everywhere.