Important tsconfig Options Explained
Understand the most important tsconfig.json options: target, module, outDir, rootDir, strict, lib, sourceMap, and more. Learn what each one controls and how to choose the right values.
A tsconfig.json can have over a hundred compiler options, but only a handful of tsconfig options matter when you are setting up a project. This article covers the options you need to understand first: what they control, why they matter, and how to pick the right value.
If you have not created a tsconfig yet, start with how to create a tsconfig file.
The essential six
These six options define what your project produces and how strictly it is checked:
| Option | What it controls | Common values |
|---|---|---|
| target | JavaScript version in output | ES2022, ES2020 |
| module | Module system in output | nodenext, preserve |
| strict | All type-checking strictness | true (recommended) |
| outDir | Where .js files are written | ./dist |
| rootDir | Root of the source tree | ./src |
| lib | Built-in type definitions | ES2022, DOM |
Set these six before anything else. The rest of this article explains each one and covers a few more options you will likely need.
target: which JavaScript version to emit
The target option tells TypeScript which version of JavaScript to produce. If you write an arrow function and target ES5, TypeScript converts it to a regular function. If you target ES2015 or higher, the arrow function stays as-is.
Choose the oldest JavaScript version your runtime needs to support. For Node.js 18 and above, ES2022 is a safe choice. For modern browsers, ES2020 or higher works well. The special value ESNext means "everything this TypeScript version supports," but it can change between TypeScript upgrades, so pinning a specific version is safer.
The target option also changes the default lib. Setting target to ES2022 automatically includes type definitions for ES2022 APIs like Array.at() and Error.cause.
module: how imports and exports are emitted
The module option controls the module system TypeScript uses when emitting JavaScript. This affects how import and export statements appear in the output. Here is how different settings affect the same code:
| module value | Emitted output style | Best for |
|---|---|---|
| nodenext | ESM or CJS based on package.json | Modern Node.js projects |
| preserve | Imports and exports left as-is | Bundled apps (Vite, Webpack) |
| ESNext | ESM import/export statements | Bundled apps, modern browsers |
| CommonJS | require() and module.exports | Legacy Node.js projects |
For a new Node.js project, use nodenext. For a frontend app bundled with Vite or Next.js, use preserve or ESNext. Do not use CommonJS for new projects unless you have a specific reason.
strict: all type checks at once
Setting strict to true enables every strict type-checking flag simultaneously. This includes noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, and several others.
You can turn individual flags off after enabling strict. For example, if noImplicitAny is too strict during a migration, set strict to true and noImplicitAny to false. This keeps all other checks active while relaxing just one.
Starting a new project? Enable strict from day one. It is much harder to add later. For details on every check it enables, see strict mode in TypeScript.
outDir and rootDir: controlling output layout
The outDir option tells TypeScript where to write compiled .js files. Without it, .js files appear next to your .ts files, which clutters your source tree.
The rootDir option tells TypeScript where your source tree starts. This controls the directory structure inside outDir. With rootDir set to ./src and outDir set to ./dist, a file at src/utils/helpers.ts becomes dist/utils/helpers.js.
If you omit rootDir, TypeScript infers it as the longest common path of all input files. This is usually correct, but set it explicitly when your source files span multiple top-level directories.
lib: which built-in APIs are available
The lib option controls which JavaScript and environment type definitions TypeScript includes. By default, lib is derived from target, but you can override it.
For a Node.js project, you typically do not include the DOM library. For a browser project, you must include it, or TypeScript will not know about document.querySelector or addEventListener.
| lib value | Provides types for |
|---|---|
| ES2022 | Array.at(), Error.cause, top-level await |
| DOM | document, window, HTMLElement, browser APIs |
| DOM.Iterable | Iterable DOM collections like NodeList |
| ESNext | The newest APIs your TypeScript version supports |
If your runtime has polyfills for newer APIs, you can set lib higher than target. For example, target ES2020 with lib set to ES2022 plus DOM gives you type definitions for ES2022 APIs while emitting ES2020-compatible syntax.
sourceMap: debugging in the browser
The sourceMap option generates .js.map files that map compiled JavaScript back to the original TypeScript source. This lets you set breakpoints and step through .ts files directly in browser DevTools or the Node.js debugger.
Always enable this during development. It adds a small build cost but makes debugging dramatically easier. In production builds you can turn it off to reduce output size, or use inlineSourceMap if your deployment setup needs it.
skipLibCheck: faster compilation
The skipLibCheck option skips type-checking of all .d.ts declaration files. This can significantly speed up compilation, especially in projects with many dependencies.
It is safe to enable in most projects. The tradeoff is that conflicting type definitions between libraries might go unnoticed. If your build suddenly breaks after adding a dependency, try turning this off temporarily to see if the problem is a type conflict in declaration files.
moduleResolution: how imports are found
The moduleResolution option controls how TypeScript resolves import paths. You rarely need to set this explicitly because it defaults sensibly based on module, but understanding it helps when imports break:
| moduleResolution | How it resolves |
|---|---|
| nodenext | Node.js ESM/CJS rules, requires file extensions in ESM |
| bundler | Like nodenext but does not require file extensions |
| node10 | Legacy Node.js resolution, CommonJS only |
For most projects, the default is correct. If you are using a bundler and imports are failing, try setting moduleResolution to bundler.
esModuleInterop: cleaner default imports
The esModuleInterop option lets you write a default-style import for CommonJS modules, which is more natural. This is enabled by default with nodenext and preserve module settings.
If you are on an older module setting, enable it manually. It also enables allowSyntheticDefaultImports, which suppresses type errors for this pattern. Without it, importing CommonJS modules requires a namespace import syntax that is less convenient.
A recommended config for new projects
Putting it all together, here is a solid starting configuration for a Node.js project:
{
"compilerOptions": {
"target": "ES2022",
"module": "nodenext",
"strict": true,
"outDir": "./dist",
"rootDir": "./src",
"sourceMap": true,
"skipLibCheck": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}This enables all the important checks, separates output from source, and targets modern Node.js. For a browser project bundled with Vite, change module to preserve and add DOM to lib:
{
"compilerOptions": {
"target": "ES2022",
"module": "preserve",
"lib": ["ES2022", "DOM"],
"strict": true,
"sourceMap": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}Both configurations start with strict enabled. That single line is the most important choice in your tsconfig.
Rune AI
Key Insights
- target controls the JavaScript version in emitted output; set it to match your runtime.
- module controls the module system; use nodenext for Node.js, preserve for bundled apps.
- strict: true enables all type-checking flags at once.
- outDir separates compiled .js from source .ts files.
- lib controls which built-in APIs TypeScript knows about; add dom for browser projects.
- sourceMap enables debugging TypeScript directly in the browser or Node inspector.
Frequently Asked Questions
What is the difference between target and lib?
Should I use esModuleInterop?
What is the best module setting for a Node.js project?
Conclusion
The most important tsconfig options are target, module, strict, outDir, rootDir, and lib. Set these six correctly from the start and your project will have the right output for its runtime, the right type definitions available, and the strongest type-checking enabled.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.