PostgreSQL

drizzle-kit: Cannot use import statement outside a module

Get drizzle-kit generate, push and migrate past an ES module import in your schema: find which loader and which Node defaults you have, then pick one of three fixes.

Mahdi Ben RhoumaMahdi Ben Rhouma
7 min read

TL;DR#

drizzle-kit 0.31.9 and every release before it read drizzle.config.ts and your schema files through esbuild-register, which turns them into CommonJS. When your schema imports a package whose files use import syntax, the require hook passes that file to Node unchanged, and an older Node compiles it as CommonJS: SyntaxError: Cannot use import statement outside a module. Upgrade to drizzle-kit 0.31.10 or later, whose release notes say it moved to the tsx loader "to use drizzle-kit seamlessly with both ESM and CJS modules". If you cannot upgrade, run the CLI through tsx, or move to a Node release that can require() ES modules on its own.

The report is drizzle-orm issue #819, filed in June 2023 against drizzle-kit 0.19.2 and drizzle-orm 0.27.0 and closed on 2 February 2026. Its stack trace ends in drizzle-kit's own loader:

text
/node_modules/.pnpm/drizzle-orm@0.27.0_postgres@3.3.5/node_modules/drizzle-orm/pg-core/index.mjs:1
import { M as TableAliasProxyHandler, N as PgColumnBuilder, e as entityKind, O as PgColumn, U as pgTableWithSchema, X as pgViewWithSchema, Y as pgMaterializedViewWithSchema, i as is } from '../alias-3edfcde9.mjs';
^^^^^^
 
SyntaxError: Cannot use import statement outside a module
    at Object.compileFunction (node:vm:360:18)
    at wrapSafe (node:internal/modules/cjs/loader:1048:15)
    at Module._compile (node:internal/modules/cjs/loader:1083:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1173:10)
    at Object.newLoader [as .mjs] (/node_modules/.pnpm/drizzle-kit@0.19.2/node_modules/drizzle-kit/index.cjs:8506:13)

Why the schema is loaded as CommonJS#

drizzle-kit is a CommonJS program (bin.cjs). To read a TypeScript config and schema, it registers a require hook, then calls require() on your files. Up to 0.31.9 that hook was esbuild-register: it compiles your .ts files to CommonJS, so import { nanoid } from 'nanoid' in your schema becomes a require('nanoid'). The frame Object.newLoader in the stack is that hook. Code inside node_modules is not transformed: it goes to Node's normal loader as it is. Whether that works depends on Node, not on drizzle-kit: The same loader also fails in projects that use nodenext and import sibling schema files as ./users.js: drizzle-kit 0.31.9 then stops with Cannot find module for a .js import under nodenext, even on a Node release that can require() ES modules.

  • A package marked "type": "module", or a .mjs file, needs Node's require(esm) support.
  • A .js file that uses import but whose package.json has no "type" field needs Node's module syntax detection to be recognised as an ES module, and require(esm) to be loaded as one. Without them, Node compiles the file as CommonJS and stops at the first import with exactly this message.

Both features changed defaults recently. The Node.js 22 changelog enables syntax detection by default in 22.7.0 and require(esm) in 22.12.0. The Node.js 20 changelog enables both in 20.19.0. Its release notes describe detection like this: "if the module fails to parse as CommonJS due to ES module syntax, Node.js tries again and runs the file as an ES module." Older Node versions do neither. Between June 2023 and its closure the issue collected reports from Expo users and from projects moving to ESM. In February 2024 a maintainer summed it up on the issue: "Drizzle Kit can't be run in esm mode, but we are working on making it possible".

Reproducing the failure on drizzle-kit 0.31.9#

Reproduced on 13 October 2026 in the official node:22 Docker image (Node v22.23.3), with drizzle-orm 0.45.4. The schema imports makeId from esm-in-js, a minimal local stand-in package written into node_modules for the test (not an npm package): a package.json with no "type" field and an index.js that starts with an import. That is the shape of the packages people hit in the issue. To get an older Node's behaviour on a current Node, the run switches off the two defaults with Node's own flags:

bash
NODE_OPTIONS='--no-experimental-require-module --no-experimental-detect-module' npx drizzle-kit generate --dialect=postgresql --schema=./src/schema-esm-in-js.ts
text
(node:662) Warning: Failed to load the ES module: /app/node_modules/esm-in-js/index.js. Make sure to set "type": "module" in the nearest package.json file or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/app/node_modules/esm-in-js/index.js:1
import { randomUUID } from "node:crypto";
^^^^^^
 
SyntaxError: Cannot use import statement outside a module
    at wrapSafe (node:internal/modules/cjs/loader:1713:18)
    at Module._compile (node:internal/modules/cjs/loader:1755:20)
    at node:internal/modules/cjs/loader:1913:10
    at Object.newLoader (/app/node_modules/drizzle-kit/bin.cjs:14319:13)
    at extensions..js (/app/node_modules/drizzle-kit/bin.cjs:16842:28)

Read it from the bottom of the frames up: drizzle-kit's hook (Object.newLoader) passed the file on, and Node's CommonJS compiler (Module._compile, wrapSafe) rejected the import line. The warning above the trace is Node noticing the module syntax too late to do anything about it.

The same command without NODE_OPTIONS generated the migration on Node v22.23.3 and on Node v20.20.2 (node:20 image), because both releases load the stand-in themselves. That control matters when you read the issue: on current Node defaults, drizzle-kit 0.31.9 loaded both stand-ins, so the error is expected on releases before 20.19.0 or 22.12.0, such as Node 18, or wherever someone has turned those defaults off.

Run the same command with a stand-in whose entry is index.mjs, and the error changes shape:

text
Error [ERR_REQUIRE_ESM]: require() of ES Module /app/node_modules/esm-mjs/index.mjs from /app/src/schema-esm-mjs.ts not supported.
    at Object.newLoader [as .mjs] (/app/node_modules/drizzle-kit/bin.cjs:14319:13)

The issue's own trace went through the same frame, newLoader [as .mjs], but there the hook handed the .mjs file to Module._extensions..js, the CommonJS compiler, which is why the reporter saw the SyntaxError for a .mjs file. On Node v22.23.3 with the flags, the same frame throws ERR_REQUIRE_ESM instead. Both errors come from one cause, an ES module required through drizzle-kit's CommonJS hook, and upgrading to 0.31.10 cleared both in the reproduction.

Check your own stack trace#

Paste the whole error, with its first at lines, to see whether the failing file was compiled as CommonJS or loaded as an ES module. It runs in your browser, nothing is sent.

Runs entirely in your browser — nothing you paste is sent anywhere. Tokens and passwords that appear in paths or URLs are masked in the output.

Optional: package.json, file extension, Node.js version (sharper advice)
Try:

Fix#

  1. Find out which loader and which Node defaults you have. A drizzle-kit below 0.31.10 together with false or undefined from the last command is the combination that breaks:
bash
npx drizzle-kit --version
node -v
node -p process.features.require_module

undefined means a Node older than the property itself: the process documentation lists process.features.require_module as added in v20.19.0, v22.10.0 and v23.0.0, so Node 18 and Node 20 before 20.19.0 print undefined, not false. false means the property exists but require(esm) is off, as with the flags used in the reproduction.

  1. Upgrade drizzle-kit to 0.31.10 or later. This is the fix the maintainers shipped on the stable line:
bash
npm install -D drizzle-kit@latest

With pnpm, as in the original report:

bash
pnpm add -D drizzle-kit@latest
  1. If you are pinned to an older drizzle-kit, run the CLI through tsx, the workaround from the issue thread. tsx transforms ES module syntax before Node compiles the file:
bash
npm install -D tsx
npx tsx node_modules/drizzle-kit/bin.cjs generate

To keep typing the same command, put it in package.json:

json
{
  "scripts": {
    "drizzle-kit": "tsx node_modules/drizzle-kit/bin.cjs"
  }
}

Then run npm run drizzle-kit -- generate.

  1. Or move to Node 20.19.0, 22.12.0 or later, where Node loads the ES module itself. Check node -p process.features.require_module again afterwards: it must print true. On the 22 line, syntax detection arrived earlier (22.7.0), so 22.12.0 has both.

If you are already on the drizzle v1 release candidate, a maintainer closed the issue saying the problem "Should be fixed in drizzle-kit@beta.13 and drizzle-orm@beta.13". The upgrade guide installs drizzle-kit@rc and changes the migrations folder (drizzle-kit up), so treat that as a migration of its own, not a quick fix for this error.

Verify the fix#

Run the command that failed again, with nothing else changed. In the reproduction, the only change was drizzle-kit 0.31.9 to 0.31.10, with the same flags still switching off require(esm) and syntax detection:

bash
npm install -D -E drizzle-kit@0.31.10
NODE_OPTIONS='--no-experimental-require-module --no-experimental-detect-module' npx drizzle-kit generate --dialect=postgresql --schema=./src/schema-esm-in-js.ts
text
1 tables
users 2 columns 0 indexes 0 fks

The .mjs stand-in that gave ERR_REQUIRE_ESM also generated on 0.31.10, and on 0.31.9 the tsx command from step 3 generated the migration under the same flags. Do not rely on the exit code to spot the failure in CI: in the 0.31.9 runs above, drizzle-kit generate printed the SyntaxError and still exited with status 0. Check that a new file appeared under your out folder instead.

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

When the import comes from Expo or React Native#

Several reports on the issue come from Expo projects. One reporter wrote that the error broke Expo support "because react native has ActionSheetIOS which uses imports with ESM syntax". Another fixed it by replacing an expo-crypto import with react-native-uuid, without knowing why it helped. Both point the same way: the schema, or a file it imports, pulled in a mobile package that drizzle-kit never needed. The schema only has to describe tables, so keep $defaultFn helpers and shared constants in files that import nothing platform-specific. That removes the ES module from drizzle-kit's path whatever version you run. These packages were not part of the reproduction above, so try the upgrade or tsx first, and treat a newer Node as untested for them. The ts-node version of this error has the same root, TypeScript compiled to require(), and the same Node 20.19.0 and 22.12.0 cut-off. For how Node decides between CommonJS and ES module for each file, through "type", the extension and syntax detection, see exports is not defined in ES module scope.

Once the schema loads, the next failure is usually about the database, not the module system. If drizzle-kit push or migrate then stops on connect ECONNREFUSED 127.0.0.1:5432 inside Docker Compose, your DATABASE_URL points at the container's own loopback: see ECONNREFUSED 127.0.0.1:5432 in Docker Compose. If drizzle-kit migrate prints Using 'pg' driver for database querying and nothing else, that is a separate 0.31.x hang with its own causes. On SQLite drivers only, drizzle-kit push can also stop on This statement does not return data, which is a driver mismatch, not a loader problem. The module error itself is a loader problem: on drizzle-kit 0.31.10 or later, or on a Node that can require() ES modules, the same schema generates without any change to your imports.

Further reading: drizzle-kit/api: Dynamic require of "fs" is not supported.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote