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.

6 min read

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:

bashbash
tsc --init

This 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:

jsonjson
{
  "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:

jsonjson
{
  "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:

jsonjson
{
  "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:

jsonjson
{
  "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:

OptionRecommendedWhy
stricttrueEnables all type-checking flags at once. The single most impactful setting.
targetES2022Controls which JavaScript version is emitted. Modern runtimes support this well.
modulenodenext or preserveSets the module system. Use nodenext for Node.js, preserve for bundled apps.
outDir./distKeeps compiled .js files separate from your .ts source.
rootDir./srcTells TypeScript where your source tree starts, controlling output structure.
esModuleInteroptrueLets 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:

jsonjson
{
  "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:

bashbash
npm install --save-dev @tsconfig/node20

After installing, reference the base in your own tsconfig. The base handles target, module, and lib, so your file only needs project-specific overrides:

jsonjson
{
  "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:

texttext
my-project/
  tsconfig.json
  src/
    index.ts
    utils.ts
  dist/          (generated by tsc)
    index.js
    utils.js

The 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

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.
RunePowered by Rune AI

Frequently Asked Questions

Can I have multiple tsconfig.json files in one project?

Yes. You can have a base tsconfig.json and extend it in subdirectories using the extends field. This is common in monorepos where different packages need different settings.

What happens if I delete my tsconfig.json?

TypeScript falls back to built-in defaults: ES5 target, CommonJS modules, and strict mode off. Your editor loses project-specific settings and you get fewer type errors, which is usually not what you want.

Do I need a tsconfig.json for every TypeScript file?

No. One tsconfig.json at the project root covers all files matched by the include patterns. You only need additional configs for sub-projects with different settings.

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.