Create a tsconfig File
A tsconfig.json file tells TypeScript how to compile your project. Learn how to create one, what the key sections mean, and which options to set first.
A tsconfig.json file tells TypeScript what your project looks like and how to compile it. Its presence in a directory marks that directory as the root of a TypeScript project.
When you run the compiler with no arguments, it looks for a tsconfig.json starting in the current directory and walking up until it finds one. Everything TypeScript does for your project flows from this single file.
The fastest way: tsc --init
The quickest way to create a tsconfig.json is with the compiler itself. Run this command in your project root:
tsc --initThis generates a file with commented-out options showing the most common settings. The generated file includes sensible defaults and documentation for each option inline. You uncomment and change only the ones you need.
Here is what a minimal, production-ready tsconfig.json looks like after you clean it up:
{
"compilerOptions": {
"target": "ES2022",
"module": "nodenext",
"strict": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}This tells TypeScript: compile files under src/, target modern JavaScript, use Node.js-style module resolution, enable all strict checks, and put the output in a dist/ folder separate from your source.
The three root fields
A tsconfig.json has a few top-level fields that define the project boundary. The most important ones are include, exclude, and files.
include
The include field is an array of glob patterns that tell TypeScript which files to compile. Paths are relative to the directory containing the tsconfig.json:
{
"include": ["src/**/*", "tests/**/*"]
}The pattern src/**/* matches every .ts, .tsx, and .d.ts file under src/ at any depth. If you enable the allowJs option, .js and .jsx files are included too. When you omit include entirely, TypeScript defaults to **/*, which means every file in the project root and below.
exclude
The exclude field removes files from the set matched by include. It defaults to excluding node_modules and other common dependency directories:
{
"include": ["src/**/*"],
"exclude": ["src/**/*.test.ts"]
}A file excluded this way can still become part of the compilation if another included file imports it. The exclude field filters what include finds, but it does not block imports from pulling files in. It is not a security mechanism.
files
Instead of glob patterns, you can list exact file paths when your project has a small, fixed set of entry points:
{
"files": ["src/index.ts", "src/cli.ts"]
}When files is set, the include field is ignored entirely. Use files for simple projects with a handful of entry files. Use include for everything else.
compilerOptions: the core settings
The compilerOptions object is where most of the configuration lives. Here are the options you should set on every new project:
| Option | Recommended | Why |
|---|---|---|
| strict | true | Enables all type-checking flags at once. The single most impactful setting. |
| target | ES2022 | Controls which JavaScript version is emitted. Modern runtimes support this well. |
| module | nodenext or preserve | Sets the module system. Use nodenext for Node.js, preserve for bundled apps. |
| outDir | ./dist | Keeps compiled .js files separate from your .ts source. |
| rootDir | ./src | Tells TypeScript where your source tree starts, controlling output structure. |
| esModuleInterop | true | Lets you use default imports from CommonJS modules naturally. |
A full treatment of the most useful options is in important tsconfig options explained. For understanding what strict mode enables specifically, see strict mode in TypeScript.
The extends field: sharing configuration
You can inherit from another config file with the extends field. The base file's settings load first, then any setting in the inheriting file overrides them:
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist"
}
}This pattern is useful in monorepos where multiple packages share most settings but differ on a few, such as the output directory. The inheriting file only contains the overrides, keeping each package's config small and focused.
You can also extend community-maintained bases from npm. These packages bundle the correct target, module, and lib settings for specific runtimes so you do not need to research them yourself. For a Node.js 20 project, install the base package and then reference it:
npm install --save-dev @tsconfig/node20After installing, reference the base in your own tsconfig. The base handles target, module, and lib, so your file only needs project-specific overrides:
{
"extends": "@tsconfig/node20/tsconfig.json",
"compilerOptions": {
"outDir": "./dist"
},
"include": ["src/**/*"]
}This pulls in the correct target, module, and lib settings for Node.js 20 automatically. Your config only contains project-specific choices.
The project structure after setup
A typical project layout after adding a tsconfig:
my-project/
tsconfig.json
src/
index.ts
utils.ts
dist/ (generated by tsc)
index.js
utils.jsThe tsconfig.json at the root controls everything under src/. The dist/ folder matches the outDir setting, so TypeScript's default exclude pattern automatically skips it. The compiler never re-compiles its own output.
Checking your configuration
Run tsc --showConfig to see the resolved configuration with all defaults filled in. This prints the effective settings after applying extends, defaults, and any CLI overrides. It is the fastest way to verify your config is doing what you think.
You can also run tsc --noEmit to type-check without producing output, which is useful in CI or when your bundler handles compilation.
Common mistakes
- Forgetting to set an output directory. Without outDir, .js files land next to your .ts files, which clutters your source tree and makes cleanup harder.
- Setting include too wide. Matching */ at the project root pulls in config files, build scripts, and fixtures from dependency directories. Scope include to your actual source directory.
- Not enabling strict mode from the start. Starting without strict means you miss many type-checking rules. If a specific check causes too much friction, turn just that one off individually while keeping the rest active.
- Confusing exclude with a safety mechanism. The exclude field only affects the glob search from include. It does not prevent a file from being compiled if an included file imports it.
Rune AI
Key Insights
- Run tsc --init to generate a tsconfig.json with sensible defaults.
- include and exclude control which files TypeScript compiles.
- compilerOptions is where you set strict, target, module, and outDir.
- Start with strict: true and relax individual checks only if needed.
- Use extends to share a base config across multiple projects.
Frequently Asked Questions
Can I have multiple tsconfig.json files in one project?
What happens if I delete my tsconfig.json?
Do I need a tsconfig.json for every TypeScript file?
Conclusion
A tsconfig.json gives TypeScript the information it needs to understand your project. Start with tsc --init, keep include and exclude simple, enable strict mode, and pick the right module and target for your runtime. The rest you can tune over time as your project grows.
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.