tsconfig.json Explained: Every compilerOptions Field
Last updated:
tsconfig.json is a JSONC file that tells the TypeScript compiler what to compile and how. Its presence marks the project root. The file has two important keys — compilerOptions (~150 settings) and include — and a handful of others. This reference covers every field you will realistically use, grouped by purpose rather than alphabetically.
Have a malformed tsconfig.json? Paste it into Jsonic's JSON Validator — it pinpoints syntax errors (tsconfig accepts comments but still must be valid JSONC).
The minimum useful tsconfig.json
An empty {} works but uses all defaults, which target ES3 and CommonJS — too old for modern code. The smallest practical config sets target, module, strict, and a couple of resolution flags. Most production projects start from a base like @tsconfig/strictest and override specific fields.
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"outDir": "dist"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}skipLibCheck: true is almost always correct for application code — it skips type-checking of .d.ts files in node_modules, which speeds up builds by 30-60% and avoids errors from upstream packages with broken types. Library authors should turn it off to catch issues in their own declarations.
target, module, moduleResolution: the output trio
These three fields decide what JavaScript syntax tsc emits and how it resolves imports. Mismatching them produces some of TypeScript's most confusing errors.
| Field | Controls | Common 2026 value |
|---|---|---|
target | Which JS syntax tsc emits | ES2022 (Node 18+, modern browsers) |
module | Output module format | NodeNext (Node ESM), preserve (bundlers), ESNext |
moduleResolution | How tsc looks up imports | NodeNext (Node), Bundler (Vite/webpack/esbuild) |
lib | Built-in type libraries available | ["ES2022", "DOM"] for browser; omit DOM for Node |
The valid module + moduleResolution combinations are not free choices. The supported pairs for TypeScript 5.5+ are:
- Modern Node (CommonJS or ESM) →
"module": "NodeNext","moduleResolution": "NodeNext" - Bundler (Vite, webpack, esbuild) →
"module": "preserve","moduleResolution": "Bundler" - Legacy Node CommonJS →
"module": "CommonJS","moduleResolution": "Node10"
Pairing "module": "ESNext" with "moduleResolution": "Node10" (a common copy-paste error) silently breaks package.json exports resolution.
strict mode: eight flags in one switch
"strict": trueturns on the strict family. You can override individual flags afterward — the order in tsconfig.json doesn't matter, last value wins.
| Flag (included in strict) | What it catches |
|---|---|
noImplicitAny | Variables and parameters without explicit types |
strictNullChecks | Forces null/undefined to be handled explicitly |
strictFunctionTypes | Contravariance of function parameters |
strictBindCallApply | Checks types of bind/call/apply |
strictPropertyInitialization | Class properties must be initialized in the constructor |
noImplicitThis | Catches implicit any for this |
alwaysStrict | Parses in strict mode + emits "use strict" prologue |
useUnknownInCatchVariables | catch (e) gets unknown instead of any |
Beyond strict, the next tier of safety flags worth enabling on most codebases: noUncheckedIndexedAccess (array/object index lookups returnT | undefined), noFallthroughCasesInSwitch, noImplicitReturns, noImplicitOverride, and exactOptionalPropertyTypes.
paths and baseUrl: import aliases
paths defines TypeScript-only import aliases. At type-check time tsc resolves them; at runtime your bundler or runtime loader must mirror the config. This is the single most error-prone field in tsconfig.json.
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@shared/*": ["../shared/src/*"]
}
}
}Each key is an alias pattern; each value is an array of fallback locations. The * wildcard matches any sub-path. TypeScript 5.0+ supports paths without baseUrl if you make the values explicit: "@/*": ["./src/*"].
Runtime support requires a matching configuration in your bundler: Vite uses resolve.alias in vite.config.ts, webpack uses resolve.alias, and tsx / ts-node reads tsconfig-paths. Forgetting the runtime side gives errors like Cannot find module '@/components/Button' at runtime even though VS Code shows no problem.
include, exclude, and files: what gets compiled
These three fields decide which files tsc reads. If none are set, tsc compiles every.ts/.tsx in the project root recursively, excludingnode_modules and the outDir.
{
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}include— glob patterns of files to compile. Globs use**for recursive,*for a single segment,?for one char.exclude— applied after include. Default excludesnode_modules,bower_components,jspm_packages, and theoutDir.files— explicit list, no globs. Use only for tiny projects or when you need precise ordering.
Critical gotcha: if a non-included file is imported from an included file, tsc still compiles it. exclude does not stop transitive imports — it only stops files from being a compilation root. To truly exclude a file from the program, do not import it.
references: project graphs for monorepos
references turns one tsconfig.json into a node in a project graph. Each referenced project gets type-checked independently, with proper build order and incremental output. Essential for monorepos and for splitting a large codebase.
// Root tsconfig.json — composes packages
{
"files": [],
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/ui" },
{ "path": "./apps/web" }
]
}
// packages/core/tsconfig.json — must enable composite
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "dist"
}
}Referenced projects must set "composite": true, which forces several constraints: declaration on, declarationMap on (default), and an explicit include or files. Build with tsc -b (build mode) instead of plain tsc. Build mode is incremental — only changed projects recompile.
tsconfig.json vs jsconfig.json
jsconfig.json is the same file with "allowJs": true implicitly set. VS Code uses it to provide intellisense and module resolution for pure JavaScript projects without making them TypeScript. Use jsconfig.json in a JS-only project for path aliases, target syntax hints, and editor refactoring; use tsconfig.json when you want type-checking too.
// jsconfig.json — JS project with @/ alias and ES2022 syntax
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"baseUrl": ".",
"paths": { "@/*": ["src/*"] },
"checkJs": false
},
"include": ["src"]
}Key terms
- compilerOptions
- The object inside tsconfig.json that holds compiler flags. Roughly 150 individual settings as of TypeScript 5.5.
- JSONC
- JSON with Comments — a relaxed JSON dialect that allows // and /* */ comments. tsconfig.json, .vscode/settings.json, and .babelrc all use JSONC.
- composite project
- A project with "composite": true that can be referenced from another tsconfig.json. Required for project references and incremental build mode (tsc -b).
- moduleResolution
- The algorithm tsc uses to resolve an import specifier to a file. Modern values: NodeNext, Bundler. Legacy: Node10 (formerly just "Node").
- extends
- A field that inherits compilerOptions from another tsconfig.json. Used with the @tsconfig org packages to share a strict base across many repos.
- strict family
- The 8 individual flags enabled by "strict": true. Setting strict: true and then disabling individual flags is the recommended way to opt out selectively.
Frequently asked questions
What does tsconfig.json do?
tsconfig.json tells the TypeScript compiler (tsc) which files to compile and how to compile them. Its presence in a directory marks that directory as the root of a TypeScript project. The compiler reads it on every invocation of tsc, every editor type-check (VS Code, WebStorm), and every build tool that wraps tsc (vite, esbuild, ts-node, Next.js). Two top-level keys do most of the work: compilerOptions (how to compile) and include/files (what to compile). Other tools like Bun, Deno, and Babel read a subset of tsconfig.json fields too.
What is the difference between "target" and "module" in tsconfig.json?
target controls which JavaScript syntax tsc emits (e.g. ES2022 keeps async/await; ES5 transpiles it). module controls the module system tsc emits (CommonJS for Node, ESNext for bundlers, NodeNext for modern Node with package.json type: module). They are independent: you can target ES2022 syntax but emit CommonJS modules. As of TypeScript 5.4+, "module": "preserve" is the recommended choice when a bundler will process the output — it preserves both import/export and CommonJS syntax untouched.
What does "strict": true enable in tsconfig.json?
"strict": true is a shortcut that enables eight individual strict-mode flags: noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, alwaysStrict, useUnknownInCatchVariables, and (TS 5.0+) strictBuiltinIteratorReturn. You can override individual flags after setting strict: true — for example "strict": true with "strictPropertyInitialization": false. Setting strict: true is the strongest single signal of a well-typed TypeScript codebase and is required by tools like next-tsc-strict and the official tsconfig/strictest base.
What is the difference between "module" and "moduleResolution"?
module sets the output module format. moduleResolution sets how TypeScript resolves import specifiers — that is, how it locates a file given an import path. The matching pairs are: module:CommonJS with moduleResolution:Node10 (legacy Node behavior), module:Node16/NodeNext with moduleResolution:Node16/NodeNext (modern Node ESM with package.json exports support), and module:ESNext with moduleResolution:Bundler (when a bundler does the resolution). Mismatched pairs cause confusing errors like "Cannot find module ... or its corresponding type declarations" even when the file exists.
How do "paths" and "baseUrl" work in tsconfig.json?
paths defines TypeScript-only import aliases relative to baseUrl. Example: {"baseUrl": ".", "paths": {"@/*": ["src/*"]}} lets you write import x from "@/components/Button" instead of "../../components/Button". paths is a TYPE-CHECK ONLY feature — at runtime, Node.js does not know about it. To actually run the code you also need a bundler (Vite, webpack, esbuild) or a runtime loader (tsx, ts-node with tsconfig-paths) that mirrors the paths config. As of TypeScript 5.0+, you can use baseUrl-less paths: {"paths": {"@/*": ["./src/*"]}} works without baseUrl.
What do "include", "exclude", and "files" do?
files is an explicit list of source files (no globs); rarely used in modern projects. include is a list of glob patterns of files to compile — default is **/* if neither files nor include is set. exclude is a list of glob patterns to omit from include; default excludes node_modules, bower_components, jspm_packages, and the outDir. include + exclude is the standard combination. Files imported transitively from included files are also compiled, even if they don't match include — so excluding a file does not stop tsc from compiling it if it is imported.
What is "extends" in tsconfig.json?
extends inherits compilerOptions from another tsconfig.json file. The path can be relative ("./tsconfig.base.json") or a package name ("@tsconfig/strictest/tsconfig.json"). The @tsconfig org publishes ready-made bases for common stacks: @tsconfig/strictest, @tsconfig/node20, @tsconfig/next, @tsconfig/recommended. Use extends for monorepos so all packages share a base config: each package has its own tsconfig.json that extends ../tsconfig.base.json and adds path-specific overrides.
Why does my IDE highlight tsconfig.json with red squiggles even though the JSON is valid?
TypeScript validates tsconfig.json against its own schema, so it flags unknown compilerOptions and invalid enum values even when the JSON itself is syntactically valid. Common causes: typo in an option name (e.g. moduleResulution instead of moduleResolution), using an option that requires a newer TypeScript version than installed, or invalid value (e.g. "target": "ES2024" before that target shipped). Hover over the squiggle for the exact diagnostic; tsc --showConfig also expands the effective config and reveals which fields were ignored or had defaults applied.
Further reading and primary sources
- TypeScript Handbook — tsconfig.json — Official tsconfig.json reference
- TypeScript — Module Resolution — The full moduleResolution algorithm with worked examples
- @tsconfig — Recommended bases — Maintained tsconfig base configs to extend from
- TSConfig Reference (autogen) — Every compilerOptions field with example output