tsconfig reference
There are well over a hundred compiler options. About twenty matter. This page is the twenty, grouped by whether you should turn them on.
The configuration this course is checked with
Printed from the object the build actually uses, so it cannot drift from what verified the samples.
{
"compilerOptions": {
"strict": true,
"target": "es2022",
"module": "esnext",
"moduleResolution": "bundler",
"moduleDetection": "force",
"lib": [
"es2023",
"dom"
],
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"noEmitOnError": false,
"removeComments": false
}
}
The skipLibCheck is a concession to build speed. The moduleDetection: "force" is unusual and specific to how this site compiles hundreds of independent samples in one pass — it makes every file a module so the samples cannot collide. You would not normally need it, though it is harmless.
The strict family
| Flag | What it catches | Lesson |
|---|---|---|
strictNullChecks | null and undefined become real types you must include on purpose. The highest-value check in the language. | 08 |
noImplicitAny | A parameter or variable the compiler cannot type. Forces annotations where inference has nothing to work from. | 01 |
strictFunctionTypes | Function parameters checked contravariantly, so a narrower callback cannot be substituted. Does not apply to method syntax. | 06 |
strictPropertyInitialization | A class field declared and never assigned. | 11 |
strictBindCallApply | bind, call and apply arguments are type-checked rather than accepted blindly. | — |
useUnknownInCatchVariables | A caught value is unknown rather than any, because throw accepts anything. | 12 |
noImplicitThis | this with an unclear type in a standalone function. | 06 |
alwaysStrict | Emits "use strict". Modules are strict anyway, so this is mostly free. | — |
Worth adding on top
| Flag | What changes | Cost | Lesson |
|---|---|---|---|
noUncheckedIndexedAccess | arr[i] becomes T | undefined, which is the truth. | Indexed loops need a check or a !. Iteration is unaffected, so preferring for...of avoids most of it. | 07 |
exactOptionalPropertyTypes | x?: T means absent or T, and not “or explicitly undefined”. | Object spreads and partial updates sometimes need restructuring. | 08 |
noImplicitOverride | override becomes mandatory when replacing an inherited member. | Almost none. Adding the keyword is mechanical and most editors will do it. | 11 |
noFallthroughCasesInSwitch | A case that falls into the next without breaking is an error. | None, unless you deliberately use fallthrough, which is rare. | 18 |
Worth considering
| Flag | What it does | Why you might not |
|---|---|---|
verbatimModuleSyntax | Requires explicit import type, so what you write is exactly what is emitted. | Nothing much. Turn it on for a new project; it needs a mechanical pass on an existing one. |
noUnusedLocals | Errors on unused variables. | Actively hostile while you are mid-thought. An ESLint rule handles this better because it can be configured per case. |
noUnusedParameters | Errors on unused parameters. | Same, plus it fights callbacks where you only need the second argument. |
noPropertyAccessFromIndexSignature | Forces obj['key'] rather than obj.key for index signatures. | Verbose, and the distinction it draws rarely matters in application code. |
isolatedModules | Rejects code that cannot be compiled one file at a time. | Required by most bundlers anyway, so usually already on. Bans const enum across files. |
erasableSyntaxOnly | Rejects the constructs that cannot be erased — enum, parameter properties, namespace. | Only useful if you want files runnable directly by Node. Then it is exactly right. |
Output and resolution
| Flag | Meaning | Typical value |
|---|---|---|
target | What syntax is emitted. A low target makes the compiler rewrite classes, async and optional chaining into much longer equivalents. | "es2022" or later |
module | What import syntax is emitted. | "preserve" with a bundler, "nodenext" for Node |
moduleResolution | How import paths are resolved, and whether file extensions are required. | "bundler" or "nodenext" |
lib | Which built-ins the compiler believes exist. Ships no code. | ["es2023", "dom"] |
outDir | Where emitted JavaScript is written. | "dist" |
rootDir | The input root, which fixes the output folder structure. | "src" |
noEmit | Check only, write nothing. Correct when a bundler builds your code. | true for apps |
declaration | Emit .d.ts files. Required to publish a typed library. | true for libraries |
sourceMap | Emit source maps, so stack traces point at your TypeScript. | true |
skipLibCheck | Do not type-check .d.ts files in dependencies. A build-speed concession, and a widely accepted one. | true |
resolveJsonModule | Allows importing .json files with real types. | true if you need it |
allowJs / checkJs | Include .js files, and check them. The migration pair. | see lesson 18 |
Flags that cost you something
| Flag | What it gives up |
|---|---|
"strict": false | Almost everything. Every check in the first table, including strictNullChecks. A codebase with this off has types that routinely lie. |
suppressImplicitAnyIndexErrors | Silences TS7053 project-wide rather than fixing the one place. Deprecated, and for good reason. |
ignoreDeprecations | Postpones dealing with a removal. Fine as a temporary measure with a note; a problem when it becomes permanent. |
"allowUnreachableCode": true | Dead code stops being reported. Rarely what you want. |
"noImplicitAny": false alone | The most common partial retreat. It sounds modest and means every un-annotated parameter silently becomes any, which spreads. |
Starting points
An application built with a bundler or framework
"strict": true,"target": "es2022","lib": ["es2023", "dom", "dom.iterable"]"module": "preserve","moduleResolution": "bundler","noEmit": true- The four extras:
noUncheckedIndexedAccess,exactOptionalPropertyTypes,noImplicitOverride,noFallthroughCasesInSwitch - Plus
verbatimModuleSyntax,isolatedModules,skipLibCheck
Code Node runs directly
- The same, but
"module": "nodenext"and"moduleResolution": "nodenext" - Write
.jsextensions in your imports, even for.tsfiles - Add
erasableSyntaxOnlyso the compiler rejects what Node cannot strip — noenum, no parameter properties @types/nodeas a dev dependency, orprocessand friends will be unknown
A library you intend to publish
- Drop
noEmit; add"declaration": trueand"declarationMap": true "outDir": "dist","rootDir": "src","sourceMap": true- Point
typesorexportsinpackage.jsonat the emitted.d.ts - Consider turning
skipLibCheckoff here, since your published types are somebody else's dependency
An existing JavaScript project
"allowJs": true,"checkJs": false,"strict": truefrom the start- Then
// @ts-checkat the top of one file at a time, with JSDoc types - Then rename leaf-first, entry points last
- Then one extra flag per pull request. Lesson 18 has the full sequence
Two commands worth knowing
npx tsc --noEmit checks everything and writes nothing. This is your continuous integration gate, and it is the most valuable line in a project's CI configuration — because most bundlers strip types without checking them, a green build otherwise tells you nothing about your types.
npx tsc --showConfig prints the fully resolved configuration, including everything inherited through extends and everything implied by strict. When you are not sure what is actually switched on, this answers it definitively rather than by reading.