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.
| Runtime | Reasonable target |
|---|---|
| Current Node.js (20+) | ES2022 or ESNext |
| Modern browsers (evergreen) | ES2021/ES2022 |
| Bundler will down-level for you | ESNext |
| Legacy / very old environments | ES2017 or lower |
targetdoes not polyfill — it only transforms syntax. If you targetES5and callArray.prototype.flat, the syntax compiles fine but the method is still missing at runtime unless you ship a polyfill. Uselibto 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
targetto the newest version your runtime supports; avoid needless down-leveling. - Remember
targettransforms syntax only — useliband polyfills for missing APIs. - Use
module: "NodeNext"withmoduleResolution: "NodeNext"for code Node runs directly. - Use
module: "ESNext"withmoduleResolution: "Bundler"when a bundler processes your code. - Keep
moduleconsistent with yourpackage.json"type"to avoid runtime load errors. - For libraries, prefer
NodeNextso dual ESM/CJS resolution and.mts/.ctswork correctly. - Reach for
ESNexttarget in bundled apps and let the bundler handle browser down-leveling.