Mert Özen Further With Every Line
Frontend TypeScript Basics — #13

tsconfig Basics: TypeScript 7 Defaults and Removed Options

Mert Özen Nov 3, 2026 8 min 2 views
tsconfig Basics: TypeScript 7 Defaults and Removed Options

We go through the strict family, target, lib, types, module, outDir and rootDir, erasableSyntaxOnly and verbatimModuleSyntax, the TypeScript 6/7 defaults and the options removed in 7.0.

Version note: The code in this post targets TypeScript 7.0 and Node 26.

Predict First

A folder contains two files. The first is a tsconfig.json with nothing but {} in it; not a single setting. The second is this discount.ts:

function applyDiscount(price, rate) {
  return price - price * rate;
}

console.log(applyDiscount(100, 0.2));

You run npx tsc --noEmit in the folder. Since you didn't write any settings, will TypeScript object to this file? If so, to what? The answer is hidden in the defaults that changed with TypeScript 6.

What Does tsconfig.json Do?

In the setup post we created a tsconfig with tsc --init and type-checked with npx tsc --noEmit. Now it's time to look inside that file. tsconfig.json says two things: which files belong to this project, and which rules the compiler applies to them. If you don't give a file list, every .ts file in the tsconfig's folder and its subfolders becomes part of the project.

When you run npx tsc without file names, the compiler reads the tsconfig.json in the current folder. If you pass file names while a tsconfig exists in the folder, you get the TS5112 error we saw in the setup post. There's also a very handy command: npx tsc --showConfig prints what the compiler understood from the tsconfig and which files it pulled into the project. If you're not sure whether a setting actually takes effect, this is the first place to look.

The Defaults of TypeScript 6 and 7

The answer to the opening question: yes, it objects. tsc reports two errors:

discount.ts(1,24): error TS7006: Parameter 'price' implicitly has an 'any' type.
discount.ts(1,31): error TS7006: Parameter 'rate' implicitly has an 'any' type.

You may have read in an older book or blog post that "TypeScript is lenient by default; you turn strict on yourself." That's no longer true. TypeScript 6.0 changed the defaults from the ground up, and 7.0 kept them as they are. Even with an empty tsconfig, the following apply: strict is on, module is esnext, target is es2025, types is an empty array ([]), and rootDir is the folder containing the tsconfig. On top of that, noUncheckedSideEffectImports is also on by default. 7.0 defines the target default as "the ECMAScript version just before esnext"; in TypeScript 7.0.2, npx tsc --help --all shows it as default: es2025.

Because strict is on, noImplicitAny is on too; that's why parameters with no annotation and no inferable type produce errors. Every example in this series is written with these defaults.

The File tsc --init Generates

We've seen that even an empty tsconfig means something. npx tsc --init adds recommended settings on top of that. When I strip the comment lines from the file TypeScript 7.0.2 generates, this is what's left:

{
  "compilerOptions": {
    "module": "nodenext",
    "target": "esnext",
    "types": [],
    "sourceMap": true,
    "declaration": true,
    "declarationMap": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "strict": true,
    "jsx": "react-jsx",
    "verbatimModuleSyntax": true,
    "isolatedModules": true,
    "noUncheckedSideEffectImports": true,
    "moduleDetection": "force",
    "skipLibCheck": true,
  }
}

Notice that this file is more ambitious than the defaults: module is nodenext, target is esnext. noUncheckedIndexedAccess, which we touched on in the arrays post, and exactOptionalPropertyTypes, which interprets optional properties more strictly, are turned on as well. sourceMap and declaration concern output files, and the jsx line only matters for projects that use JSX. The comment lines in the file also keep commonly used settings ready, such as rootDir, outDir and "types": ["node"] for Node.

The strict Family

strict isn't a single rule; it's an umbrella. When it's on, these eight settings are on too: noImplicitAny, noImplicitThis, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, strictBuiltinIteratorReturn and useUnknownInCatchVariables. Each one has the same default in the tsc help: "true unless strict is false." We met strictNullChecks and noImplicitAny in the basic types post; the others will come up when we talk about classes, function compatibility and error handling.

If you need to turn off a single member of the family, you leave strict as is and write that setting separately:

{
  "compilerOptions": {
    "strict": true,
    "strictPropertyInitialization": false
  }
}

Only do this while migrating an old codebase step by step. In a new project, it's best not to touch strict. alwaysStrict, once part of the family, is now always on; setting it to false is an error in 7.0.

target, lib and types

target determines which ECMAScript version the JavaScript produced by the compiler is written for. Let's compile the same source with two different targets:

export function getPrice(price: number | null) {
  return price ?? 0;
}

With the default es2025, the output stays return price ?? 0;, because ?? already exists in that version. With --target es2019, tsc rewrites the same line into a form older engines understand:

export function getPrice(price) {
    return price !== null && price !== void 0 ? price : 0;
}

The lowest supported target is now es2015; ES5 is history. For Node 26 and current browsers, the default is more than enough.

lib determines which built-in APIs your code can see. If you leave it out, you get the ECMAScript types matching your target plus the browser's dom types; since TypeScript 6.0, dom also covers dom.iterable, which used to be listed separately. If you write lib by hand, you decide the whole list. Write "lib": ["es2025"] and use document, and tsc immediately tells you what you did: Cannot find name 'document'. Do you need to change your target library? Try changing the 'lib' compiler option to include 'dom'. (TS2584).

types is about the packages under node_modules/@types. TypeScript used to pull every @types package it found into the project automatically; now the default is [], meaning none. Write process in a file that touches Node APIs and you'll see this:

error TS2591: Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.

The message even tells you the fix: npm i -D @types/node and "types": ["node"] in your tsconfig.

module and moduleResolution

module says which module system the generated JavaScript is written for. The default is esnext, so import and export lines stay as they are in the output. moduleResolution determines how the file is found for a line like import { x } from "./user.ts". According to the tsc help, its default is nodenext if module is nodenext, node16 if it's node16 or node18, and bundler in every other case.

In practice there are two paths. If your code runs on Node, write "module": "nodenext"; TypeScript then applies Node's file lookup rules exactly. If your code goes through a bundler, the default bundler resolution fits. We'll leave the details to the modules post in the intermediate series.

Output: noEmit, outDir and rootDir

If you run your examples with node file.ts, tsc doesn't need to produce JavaScript; Node strips the types itself. In that case you write "noEmit": true, and tsc only type-checks.

When you do want to produce JavaScript, you use outDir so the output doesn't get scattered next to your source files. Here a change that came with TypeScript 6 may surprise you. If your sources are in a src folder and you only wrote "outDir": "dist", tsc says:

error TS5011: The common source directory of 'tsconfig.json' is './src'. The 'rootDir' setting must be explicitly set to this or another path to adjust your output's file layout.
  Visit https://aka.ms/ts6 for migration information.

The reason is that rootDir is now the folder containing the tsconfig. Left like that, the output would land in a path like dist/src/main.js; instead of doing that silently, tsc leaves the decision to you. The fix is two lines:

{
  "compilerOptions": {
    "rootDir": "./src",
    "outDir": "./dist"
  }
}

erasableSyntaxOnly and verbatimModuleSyntax

These two settings guarantee that your code can run just by stripping its types; that's exactly what node file.ts does. erasableSyntaxOnly, which came with TS 5.8, forbids TypeScript-specific syntax that has a runtime counterpart. verbatimModuleSyntax, which came with TS 5.0, asks you to mark explicitly with import type or type when you import something that is only a type; that way, which imports remain in the output is never left to guesswork.

// in user.ts: export interface User { name: string }

// Error: 'User' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled.
import { User } from "./user.ts";

// Error: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.
enum Role {
  Admin,
  Guest,
}

The first error is TS1484, the second TS1294. The fixes are import type { User } from "./user.ts"; and some other construct instead of the enum, respectively; we'll talk separately in the intermediate series about why enums run into this rule. This is the tsconfig I use to run the examples in this series:

{
  "compilerOptions": {
    "module": "nodenext",
    "noEmit": true,
    "allowImportingTsExtensions": true,
    "erasableSyntaxOnly": true,
    "verbatimModuleSyntax": true
  }
}

allowImportingTsExtensions lets you write the .ts extension in imports; Node requires that extension anyway. When working with nodenext, don't forget "type": "module" in package.json either; otherwise TypeScript treats your files as CommonJS and objects to your import lines with TS1295.

Common Pitfalls: Options Removed in 7.0

An old tsconfig you copy from the internet will most likely not work with TypeScript 7. The options deprecated in 6.0 turned straight into errors in 7.0:

error TS5108: Option 'target=ES5' has been removed. Please remove it from your configuration.
error TS5102: Option 'baseUrl' has been removed. Please remove it from your configuration.
  Use '"paths": {"*": ["./*"]}' instead.

Here's the full list: "target": "es5", downlevelIteration, node (that is, node10) and classic for moduleResolution, amd, umd, system and none for module, baseUrl, outFile, setting esModuleInterop or allowSyntheticDefaultImports to false, and "alwaysStrict": false. In TypeScript 6 you could silence these warnings with "ignoreDeprecations": "6.0"; in 7.0.2 the error stays even if you add that line. The only fix is to remove the option or move to its modern equivalent.

The second pitfall is assuming node file.ts reads your tsconfig. It doesn't; tsconfig only guides tsc and your editor. Node strips the types and runs the code, that's all. The third is writing lib by hand and forgetting dom: if you're writing browser code, you have to add it to the list.

A Small Experiment

★☆☆ Run npx tsc --init in an empty folder and compare the generated file with the list in this post. Then use npx tsc --showConfig to see what the compiler sees.

★★☆ Predict whether it compiles: you add "target": "es5" to your tsconfig and run npx tsc --noEmit. If it fails, what's the error code? Then change the line to "target": "es2019", compile the getPrice example and look at what happened to ?? in the output.

★★☆ Write "lib": ["es2025"] and add a line that uses document.title. Read the error, then fix it by adding "dom" to the list.

★★★ Set up a small project with src/main.ts and src/user.ts. Get it through type checking with this post's series tsconfig, then remove noEmit and send the output to a dist folder with rootDir and outDir. Use tsc's messages to find out which extra setting you need for imports with the .ts extension.

Now you know which rules the compiler works by. In the next post we'll take that knowledge to the browser: we'll look at the lib.dom types, the return types of querySelector, event types, and running code compiled with tsc in an HTML page with <script type="module">.