Skip to content
TypeScript ts config 3 min read

target & module

Two compilerOptions decide what the emitted JavaScript actually looks like: target and module. target controls the ECMAScript language level — which syntax and APIs are down-leveled versus left as-is. module controls the module system of the output — whether the compiler emits import/export (ESM), require/module.exports (CommonJS), or another format. They are independent settings, and getting both right is the difference between code that runs and code that throws at startup. This page explains each and shows how to pick values for common runtimes.

What target controls

target tells the compiler the lowest JavaScript version your runtime supports. Newer syntax (optional chaining, class fields, async/await) is transpiled down when the target predates it, and the right lib defaults are selected for that level.

{
  "compilerOptions": {
    "target": "ES2022"
  }
}

With target: "ES2022", modern features like top-level class fields and Error.cause are emitted as-is. Lower it to ES2017 and the compiler rewrites newer syntax into equivalents the older engine understands. Setting target: "ESNext" tracks the latest stable features and emits the least transformation.

Choosing a target value

Pick the newest target your deployment target supports — modern Node and evergreen browsers handle recent ES versions natively, so down-leveling just bloats output.

RuntimeReasonable target
Current Node.js (20+)ES2022 or ESNext
Modern browsers (evergreen)ES2021/ES2022
Bundler will down-level for youESNext
Legacy / very old environmentsES2017 or lower

target does not polyfill — it only transforms syntax. If you target ES5 and call Array.prototype.flat, the syntax compiles fine but the method is still missing at runtime unless you ship a polyfill. Use lib to control which APIs the type checker assumes exist.

What module controls

module decides the module format of the emitted JavaScript. This must match how your runtime or bundler loads files. The common values are ESNext/ES2022 (native ESM), CommonJS (Node’s classic format), and NodeNext/Node16 (Node’s dual ESM+CJS resolution and emit).

{
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext"
  }
}

NodeNext is the correct choice for modern Node libraries: it makes the compiler honor your package.json "type" field and the .mts/.cts extensions, emitting CommonJS or ESM per file accordingly. Pair it with the matching moduleResolution.

Matching module to your environment

The format you emit has to agree with how the code is consumed.

// App bundled by Vite/esbuild/webpack — let the bundler handle modules
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "Bundler"
  }
}
// Pure Node ESM package (package.json has "type": "module")
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext"
  }
}

For bundled front-end apps, emit ESNext modules and let the bundler resolve and transform. For code Node runs directly, use NodeNext so the emitted format matches your package type.

How target and module interact

They are orthogonal but both affect output. You can target old syntax yet emit ESM, or target new syntax yet emit CommonJS. A frequent mistake is leaving module: "CommonJS" in a project whose package.json declares "type": "module", producing require calls that Node refuses to run.

// ❌ Mismatch: emits require() but Node treats files as ESM
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "CommonJS"
  }
  // package.json: { "type": "module" }  -> runtime error
}

The fix is to align module with the package type (use NodeNext) or remove "type": "module". When a bundler is in the pipeline, set module: "ESNext" and let the bundler own the final format.

Best Practices

  • Set target to the newest version your runtime supports; avoid needless down-leveling.
  • Remember target transforms syntax only — use lib and polyfills for missing APIs.
  • Use module: "NodeNext" with moduleResolution: "NodeNext" for code Node runs directly.
  • Use module: "ESNext" with moduleResolution: "Bundler" when a bundler processes your code.
  • Keep module consistent with your package.json "type" to avoid runtime load errors.
  • For libraries, prefer NodeNext so dual ESM/CJS resolution and .mts/.cts work correctly.
  • Reach for ESNext target in bundled apps and let the bundler handle browser down-leveling.
Last updated June 29, 2026
Was this helpful?