Skip to content
Latchkey

Node CJS/ESM Default Import "is not a function" in CI - Fix the Interop

When ESM imports a CommonJS module, Node wraps module.exports as the default export. Calling the namespace, or the wrong default level, throws "is not a function".

What this error means

An ESM file imports a CommonJS package and calling the default import throws TypeError: <name> is not a function, because the callable lives one level deeper.

node
import express from 'express';
const app = express();
              ^

TypeError: express is not a function
    at file:///home/runner/work/app/app/src/server.mjs:2:15

Common causes

Interop default wrapping a CommonJS export

Node exposes module.exports as default, so the real function may be express.default rather than express when interop differs.

A bundler default-interop setting mismatch

esModuleInterop or a bundler setting changes whether the default is the function or a wrapper object.

How to fix it

Resolve the callable from the namespace

  1. Import the namespace and read .default if the namespace itself is not callable.
  2. Use the resolved value as the function.
JavaScript
import pkg from 'legacy-cjs';
const create = pkg.default ?? pkg;

Enable esModuleInterop when compiling

  1. Set esModuleInterop in tsconfig so default imports of CommonJS resolve to the callable.
  2. Rebuild and re-run.
tsconfig.json
// tsconfig.json
"esModuleInterop": true

How to prevent it

  • Keep esModuleInterop consistent across build and runtime, prefer named imports where a package exposes them, and verify the interop shape when mixing CommonJS and ESM.

Related guides

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