The control panel for TypeScript
tsconfig.json tells the TypeScript compiler how to check and build your code: which files to include, how strict to be, what JavaScript version to emit, and how to resolve imports. It is JSON (technically JSONC — comments allowed), and a handful of options account for most of what you will ever change.
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src"]
}The structure
compilerOptions— the bulk of the config; how the compiler behaves.include/exclude— which files are part of the project.extends— inherit from a base config (great for monorepos and shared presets).files— an explicit list when you do not want globbing.
The options that matter most
| Option | What it controls |
|---|---|
target | JavaScript version to emit (ES2022, ESNext). |
module | Module system of the output (ESNext, CommonJS). |
moduleResolution | How imports are found (bundler, node16). |
strict | Master switch for all strict type checks. |
jsx | JSX handling for React (react-jsx). |
outDir / rootDir | Where compiled files go / where sources live. |
lib | Which built-in type definitions to include (DOM, ES2022). |
Turn on strict (and leave it on)
"strict": true enables a family of checks that catch real bugs — noImplicitAny, strictNullChecks, and more. It is the single most valuable setting in the file. Starting a new project without it is choosing to find bugs at runtime instead of compile time. On a legacy codebase, enable it and fix errors incrementally rather than leaving it off.
Path aliases
paths (with baseUrl) let you replace fragile relative imports with clean aliases:
{
"compilerOptions": {
"baseUrl": ".",
"paths": { "@/*": ["./src/*"] }
}
}Now import { x } from "@/lib/util" works from anywhere. Note that your bundler must understand the same aliases — TypeScript only handles type resolution, not the runtime path rewrite.
Compiler-only vs build settings
A subtlety that confuses people: in many modern setups (Next.js, Vite), a bundler — not tsc — actually builds your code. There, TypeScript is used mainly for type checking, and options like noEmit: true tell it not to output files. Settings like target and module may be overridden by the bundler. Know who is doing the compiling in your stack.
A sensible modern baseline
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM"],
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"esModuleInterop": true,
"skipLibCheck": true,
"noEmit": true,
"paths": { "@/*": ["./src/*"] },
"baseUrl": "."
},
"include": ["src"]
}