Every TypeScript project has (or should have) a tsconfig.json file at its root. It tells the TypeScript compiler how to turn your .ts code into JavaScript: which type rules to apply, which JavaScript version to generate, and which files to include or ignore in that process. This post explains the most important options.
An interesting detail: tsconfig.json isn’t technically pure JSON
Before getting into the fields, worth a clarification: TypeScript allows comments inside tsconfig.json, which technically isn’t valid in standard JSON (as covered in other posts on the blog about parsing errors). This works because the TypeScript compiler uses a lenient parser (sometimes called JSON5 or “JSONC”, JSON with Comments) specifically for this file. If you try to open a tsconfig.json with comments using plain JSON.parse(), you’ll get a syntax error.
A basic tsconfig.json
// jsonc
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
The main compilerOptions fields
target: defines which JavaScript version TypeScript should compile to. ES2020, for example, generates code compatible with browsers/Node.js that support that standard. Older versions (ES5) generate more compatible but more verbose code, since modern features need to be “translated” into older syntax.
module: defines the module system of the generated code. commonjs generates require()/module.exports (Node.js’s traditional default), while esnext or es2020 keeps native import/export.
strict: turns on a stricter set of type checks all at once (equivalent to enabling several individual flags, like strictNullChecks, noImplicitAny, among others). Recommended for new projects, since it catches more errors at compile time.
outDir and rootDir: define where the TypeScript source code lives (rootDir, usually ./src) and where the compiled JavaScript should go (outDir, usually ./dist or ./build).
include and exclude
// jsonc
{
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
include: defines which files the compiler should process. The pattern src/**/* means “every file inside the src folder, at any depth.”
exclude: defines what should be ignored, even if it would otherwise match the include pattern. node_modules is almost always excluded, since those are already-compiled dependencies, not your project’s own source code.
strictNullChecks: an option worth highlighting
Within strict, one of the most impactful checks is strictNullChecks. Without it, TypeScript allows assigning null or undefined to any variable, even if the declared type doesn’t explicitly include that.
// typescript
// Without strictNullChecks
let name: string = null; // allowed, even though it's odd
// With strictNullChecks
let name: string = null; // compile error
let name: string | null = null; // correct, needs to be declared explicitly
This prevents an entire category of bugs related to unhandled null values, one of the most common causes of runtime errors in plain JavaScript.
Extends: reusing configuration
If you have multiple projects (or packages in a monorepo) sharing the same base configuration, extends avoids repeating everything in each tsconfig.json.
// tsconfig.base.json
{
"compilerOptions": {
"target": "ES2020",
"strict": true
}
}
// a specific package's tsconfig.json
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist"
}
}
The file using extends inherits every option from the base file, and can override or add specific options.
paths: creating import shortcuts
A useful option in larger projects is configuring path aliases, avoiding long and fragile relative imports.
// jsonc
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@utils/*": ["src/utils/*"],
"@components/*": ["src/components/*"]
}
}
}
This lets you write import { formatDate } from '@utils/date' instead of import { formatDate } from '../../../utils/date', regardless of how deep the file sits in the folder structure.
Quick checklist
- Is
strictenabled, especially on new projects? - Are
include/excludeset up correctly, without accidentally includingnode_modules? - If there are multiple packages/projects, did you consider using
extendsto avoid duplicating configuration? - Did you remember that
tsconfig.jsonaccepts comments, but that’s not standard JSON (it only works in this specific file, with this specific parser)?
If you need to validate another .json file in your project (one that doesn’t accept comments), paste it into JSON Validator to confirm the syntax.