tsconfig.json Reference
The tsconfig.json file is the control center for the TypeScript compiler. It tells TypeScript which files to include, how to check them, what to emit, and how to resolve modules.
This reference covers the most important compilerOptions, organized by category, with explanations and recommended settings for modern projects.
File Structure
{
"compilerOptions": {
// Compiler behavior settings go here
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist"
],
"files": [
// Explicit list of files (rarely needed)
],
"extends": "./tsconfig.base.json",
"references": [
// Project references for monorepos
{ "path": "../shared" }
]
}include, TypeScript includes all .ts, .tsx, and .d.ts files in the project root recursively (excluding node_modules).Category Overview
Category | Key Options | Purpose |
|---|---|---|
Type Checking | strict, noImplicitAny, strictNullChecks | How strict to be about type errors |
Modules | module, moduleResolution, baseUrl, paths | How to resolve imports |
Emit | target, outDir, declaration, sourceMap | What to output and where |
Language & Env | lib, jsx, experimentalDecorators | Runtime environment and features |
JavaScript Support | allowJs, checkJs | Working with .js files |
Interop | esModuleInterop, allowSyntheticDefaultImports | CommonJS / ESM compatibility |
Projects | composite, incremental, tsBuildInfoFile | Monorepo and build caching |
Paths | baseUrl, paths, rootDir, outDir | Directory and alias configuration |
Type Checking Options
These options control how strictly TypeScript checks your code. The strict flag is a shorthand that enables a bundle of checks at once.
{
"compilerOptions": {
"strict": true,
// Shorthand that enables all of the following:
// strictNullChecks, noImplicitAny, strictFunctionTypes,
// strictBindCallApply, strictPropertyInitialization,
// noImplicitThis, useUnknownInCatchVariables,
// alwaysStrict (adds 'use strict' to emitted files)
// Individual type-checking flags:
"strictNullChecks": true,
"noImplicitAny": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"useUnknownInCatchVariables": true,
"alwaysStrict": true,
// Additional strict-style checks (not in 'strict'):
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noPropertyAccessFromIndexSignature": true
}
}Option | Default | What it does |
|---|---|---|
strict | false | Enables all strict type-checking flags |
strictNullChecks | false (true with strict) | null and undefined are distinct types |
noImplicitAny | false (true with strict) | Error on variables inferred as any |
strictFunctionTypes | false (true with strict) | Checks function parameter types contravariantly |
strictPropertyInitialization | false (true with strict) | Class properties must be set in constructor |
noImplicitThis | false (true with strict) | Error when this has implicit any type |
useUnknownInCatchVariables | false (true with strict) | catch(e) type is unknown instead of any |
noUnusedLocals | false | Error on declared but unused local variables |
noUnusedParameters | false | Error on declared but unused function parameters |
exactOptionalPropertyTypes | false | Disallows explicitly assigning undefined to optional props |
noImplicitReturns | false | Error if not all code paths return a value |
noFallthroughCasesInSwitch | false | Error on fallthrough between switch cases |
noUncheckedIndexedAccess | false | Array/index access returns T | undefined |
strictNullChecks in Detail
// Without strictNullChecks: let name: string = null; // OK (null is assignable to anything) let age: number = undefined; // OK // With strictNullChecks: let name: string = null; // Error: null not assignable to string let age: number = undefined; // Error: undefined not assignable to number // Fix: use union types to allow null/undefined explicitly let name: string | null = null; let age: number | undefined = undefined; // Optional chaining and nullish coalescing become essential const upper = name?.toUpperCase() ?? 'anonymous';
noUncheckedIndexedAccess in Detail
// Without noUncheckedIndexedAccess:
const arr: string[] = ['a', 'b', 'c'];
const first: string = arr[0]; // OK (but arr[10] would also type as string)
// With noUncheckedIndexedAccess:
const first: string | undefined = arr[0]; // index access adds | undefined
const safe = arr[0]?.toUpperCase(); // must handle undefined
// Also affects Record types
const dict: Record<string, number> = { a: 1 };
const val: number | undefined = dict['b']; // undefined is possiblenoUncheckedIndexedAccess in an existing codebase will generate many new errors. Enable it on new projects or gradually fix violations in existing code.Module Options
{
"compilerOptions": {
// What module format to emit
"module": "NodeNext", // Options: CommonJS, ESNext, NodeNext, Node16, Preserve
// How to resolve modules from import paths
"moduleResolution": "NodeNext", // Options: Node10, Node16, NodeNext, Bundler
// Base directory for non-relative imports
"baseUrl": "./src",
// Path aliases
"paths": {
"@components/*": ["components/*"],
"@utils/*": ["utils/*"],
"@types/*": ["types/*"]
},
// Root directory of source files (for output mirroring)
"rootDir": "./src",
// Whether to allow importing .ts extension explicitly
"allowImportingTsExtensions": true,
// Resolve JSON files as modules
"resolveJsonModule": true
}
}module value | Use case |
|---|---|
CommonJS | Node.js projects using require/module.exports |
ESNext | Bundler environments (webpack, Vite, esbuild) |
NodeNext | Modern Node.js with native ESM support (.mjs/.cjs) |
Node16 | Same as NodeNext (alias) |
Preserve | Pass through imports as-is (TS 5.4+, for bundlers) |
None | Single-file scripts with no imports |
moduleResolution value | When to use |
|---|---|
Node10 (legacy) | Old projects — resolves like classic Node.js require |
Node16 | Modern Node.js with ESM — requires file extensions in imports |
NodeNext | Same as Node16 but tracks future Node.js changes |
Bundler | Vite, webpack, esbuild — no extension needed, supports package.json exports |
Classic | Very old TypeScript — avoid |
paths and baseUrl
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
// Single path alias
"@config": ["config/index.ts"],
// Wildcard alias (maps any @utils/* to src/utils/*)
"@utils/*": ["utils/*"],
// Multiple fallback paths
"@shared/*": ["../shared/src/*", "shared/*"]
}
}
}// With the above config, you can write:
import { AppConfig } from '@config';
import { formatDate } from '@utils/date';
// Instead of:
import { AppConfig } from '../../config';
import { formatDate } from '../../../utils/date';paths in Node.js projects, you also need a runtime path resolver (e.g., tsconfig-paths or build tool support) because TypeScript only handles compile-time resolution.Emit Options
{
"compilerOptions": {
// ECMAScript version to compile down to
"target": "ES2020", // Options: ES3, ES5, ES2015...ES2023, ESNext
// Output directory for compiled files
"outDir": "./dist",
// Don't emit any output files (type-check only)
"noEmit": true,
// Generate .d.ts declaration files
"declaration": true,
// Output directory for declaration files (if different from outDir)
"declarationDir": "./types",
// Generate .d.ts.map files (source maps for declarations)
"declarationMap": true,
// Generate .js.map source maps
"sourceMap": true,
// Inline source map as data URL in the .js file
"inlineSourceMap": false,
// Remove all comments from emitted files
"removeComments": false,
// Don't emit on compilation errors
"noEmitOnError": true,
// Preserve const enums as their literal values
"preserveConstEnums": false,
// Emit helpers inline instead of importing from tslib
"importHelpers": true,
// Only emit declaration files (no JS)
"emitDeclarationOnly": false
}
}target value | JS features available | Use case |
|---|---|---|
ES3 | Basic JS only | Legacy browser support |
ES5 | let, const, arrow functions | IE11 support |
ES2015 (ES6) | Classes, Promises, generators | Modern browsers |
ES2017 | async/await natively | Node.js 8+, modern browsers |
ES2020 | Optional chaining, nullish coalescing, BigInt | Node.js 14+, modern browsers |
ES2022 | Top-level await, class fields | Node.js 16+ |
ESNext | Latest JS features | Always current with bundlers |
Language and Environment Options
{
"compilerOptions": {
// Type definition libraries to include
"lib": ["ES2020", "DOM", "DOM.Iterable"],
// Common combinations:
// Node.js: ["ES2020"] (no DOM) + install @types/node
// Browser: ["ES2020", "DOM", "DOM.Iterable"]
// Web Workers: ["ES2020", "WebWorker"]
// JSX transformation mode
"jsx": "react-jsx",
// Options:
// "preserve" — output .jsx, no transformation
// "react" — React.createElement calls (React 16)
// "react-jsx" — import { jsx } from 'react/jsx-runtime' (React 17+)
// "react-jsxdev" — same as react-jsx but development build
// "react-native" — preserve JSX for React Native bundler
// Enable legacy decorators (before Stage 3)
"experimentalDecorators": false,
// Enable metadata for decorators (used by reflection-based DI)
"emitDecoratorMetadata": false,
// Use define semantics for class fields
"useDefineForClassFields": true
}
}lib in Detail
lib entry | What it provides |
|---|---|
ES5 | Basic ES5 types (Array, Object, Math, etc.) |
ES2015 (ES6) | Promise, Map, Set, Symbol, generators, iterators |
ES2017 | SharedArrayBuffer, Atomics, async/await types |
ES2020 | BigInt, nullish coalescing, optional chaining, globalThis |
ES2022 | Array.at(), Object.hasOwn(), class static blocks |
ESNext | Latest proposed features |
DOM | Browser DOM APIs (document, window, HTMLElement, etc.) |
DOM.Iterable | Iterable versions of DOM collections |
WebWorker | Web Worker globals (self, postMessage, etc.) |
ScriptHost | Windows Script Host environment |
JavaScript Support Options
{
"compilerOptions": {
// Allow importing .js files
"allowJs": true,
// Type-check .js files (respects JSDoc annotations)
"checkJs": true,
// Max errors to report (0 = unlimited)
"maxNodeModuleJsDepth": 0
}
}// With allowJs and checkJs, TypeScript understands JSDoc:
// In a .js file:
/**
* @param {string} name
* @param {number} age
* @returns {string}
*/
function greet(name, age) {
return `${name} is ${age} years old`;
}
// TypeScript will check that callers pass (string, number) -> stringallowJs + checkJs when migrating a JavaScript codebase to TypeScript incrementally. Start by checking existing .js files without converting them.Interop Options
{
"compilerOptions": {
// Enables interop between CommonJS and ES modules
// Allows: import React from 'react' (default import for CJS module)
"esModuleInterop": true,
// Allows default imports from modules with no default export
// (implied when esModuleInterop is true)
"allowSyntheticDefaultImports": true,
// Force consistent file name casing in imports
"forceConsistentCasingInFileNames": true,
// Isolate each file as its own module (required by Babel/esbuild/SWC)
"isolatedModules": true
}
}// Without esModuleInterop: import * as React from 'react'; // required import * as path from 'path'; // With esModuleInterop: true: import React from 'react'; // default import works import path from 'path'; // default import works
isolatedModules: true is required when you use a transpiler like Babel, esbuild, or SWC that processes files individually (not the full TypeScript compiler). It disables features like const enums and ambient module declarations that need cross-file knowledge.Project / Build Options
{
"compilerOptions": {
// Enable incremental compilation (stores build info)
"incremental": true,
// Where to store the incremental build info file
"tsBuildInfoFile": "./.tsbuildinfo",
// Enable project references (for monorepos and large projects)
"composite": true,
// Skip type checking of all declaration files (.d.ts)
"skipLibCheck": true,
// Skip type checking of default lib declaration files only
"skipDefaultLibCheck": false
}
}Project references allow you to split large TypeScript projects into smaller pieces that can be compiled independently and incrementally.
// Root tsconfig.json in a monorepo
{
"files": [],
"references": [
{ "path": "./packages/shared" },
{ "path": "./packages/api" },
{ "path": "./packages/web" }
]
}
// packages/api/tsconfig.json
{
"compilerOptions": {
"composite": true,
"outDir": "./dist",
"rootDir": "./src"
},
"references": [
{ "path": "../shared" } // depends on shared
]
}Strict Mode Bundle
Setting "strict": true is equivalent to enabling all of these individually:
strictNullChecks — null and undefined are their own types
noImplicitAny — error when TypeScript infers any
strictFunctionTypes — function parameters checked contravariantly
strictBindCallApply — call/bind/apply are type-checked
strictPropertyInitialization — class properties must be initialized
noImplicitThis — error when this has type any
useUnknownInCatchVariables — catch(e) is unknown, not any
alwaysStrict — emits "use strict" in every file
"strict": true on new projects. It catches a large class of runtime bugs at compile time and makes TypeScript genuinely safer to work with.Recommended Configurations by Project Type
Node.js (Modern ESM)
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}React / Vite / Bundler
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}Next.js
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [{ "name": "next" }],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}Common Mistakes
Mistake | Problem | Fix |
|---|---|---|
Omitting strict | Many bugs TypeScript could catch are missed | Add "strict": true |
Wrong moduleResolution for project type | Imports fail at runtime or compile time | Match moduleResolution to your runtime/bundler |
baseUrl without paths entries | Short imports still fail | Add explicit paths entries or use bundler config |
skipLibCheck: false with bad @types | Errors from third-party type files block builds | Use "skipLibCheck": true |
target too high for runtime | Code fails in older runtimes | Match target to minimum supported runtime |
No include/exclude | TS compiles test files into production output | Add "exclude": ["**/*.test.ts"] |
esModuleInterop missing with CJS modules | import X from 'y' fails for CJS modules | Add "esModuleInterop": true |