Skip to content
Latchkey

Node.js "ts-node ESM loader error" in CI

Running TypeScript as native ESM with ts-node requires the ESM loader and matching tsconfig module settings. Without them, ts-node either throws on .ts extensions or fails to resolve ESM imports.

What this error means

A script run via ts-node under "type": "module" fails in CI with a loader or unknown-extension error, even though plain tsc compiles the same files.

node
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension
".ts" for /work/repo/src/main.ts
    at Object.getFileProtocolModuleFormat

Common causes

The ESM loader is not enabled

ts-node defaults to CommonJS. Under ESM you must register ts-node/esm as a loader, or Node does not know how to handle .ts.

tsconfig module settings mismatch

If module/moduleResolution are not ESM-aware, ts-node emits or resolves modules in a way that conflicts with the ESM runtime.

How to fix it

Register the ts-node ESM loader

Use the module loader flag so Node hands .ts files to ts-node under ESM.

Terminal
node --loader ts-node/esm src/main.ts
# or on newer Node:
node --import ts-node/esm/transpile-only src/main.ts

Align tsconfig with ESM

Set ESM-aware module resolution so ts-node and the runtime agree.

tsconfig.json
{
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "NodeNext"
  },
  "ts-node": { "esm": true }
}

How to prevent it

  • Consider tsx instead of ts-node for simpler ESM execution.
  • Pin Node and ts-node versions so loader flags stay consistent.
  • Keep tsconfig module settings ESM-aware when running ESM.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →