PostgreSQL

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

Get pushSchema, generateDrizzleJson and generateMigration importing from an ES module or a Vitest suite again: check your drizzle-kit version, upgrade, or fall back to createRequire.

Mahdi Ben RhoumaMahdi Ben Rhouma
7 min read

TL;DR#

On stable drizzle-kit 0.x releases before 0.31.6, import { pushSchema } from "drizzle-kit/api" in an ES module stops with Error: Dynamic require of "fs" is not supported. The ES module build, api.mjs, bundles CommonJS dependencies whose require("fs") calls need a require function. An ES module does not have one, so the bundler's fallback throws while the file is still loading. Upgrade drizzle-kit to 0.31.6 or later: its release notes list this exact issue as fixed. If you are pinned to an older release, load the CommonJS build through createRequire instead.

The report is drizzle-orm issue #2853, filed on 27 August 2024 against drizzle-kit 0.24.2 and drizzle-orm 0.33.0. The reporter hit it while calling pushSchema from unit tests in a TypeScript ESM package, then cut it down to a three-line index.mjs in a package with "type": "module". The same import through require() in an index.cjs file worked. A maintainer closed the issue on 28 October 2025 with "Fixed in drizzle-kit@0.31.6".

The error, reproduced#

The reproduction ran on 30 October 2026 directly on a Windows 11 machine with Node v26.7.0 and npm 11.19.0, not in a container, with drizzle-kit 0.31.5 (the last release before the fix) and drizzle-orm 0.44.7. The project's package.json had "type": "module", and index.mjs was the reporter's file:

javascript
// index.mjs
import { pushSchema } from "drizzle-kit/api";
 
const _ = pushSchema;
 
console.log("drizzle-kit API imported");

node index.mjs printed the same error as the report, followed by a stack trace through fs.realpath, glob and drizzle-kit's serializer, and exited with code 1:

text
  throw Error('Dynamic require of "' + x + '" is not supported');
        ^
 
Error: Dynamic require of "fs" is not supported

The code never calls pushSchema. Only importing it is enough to fail. Under Vitest 5.0.5 the same import in a test file fails the whole suite before any test runs, which is the situation from the original report. The Vitest trace is easier to read because it shows the bundled module each frame comes from:

text
 FAIL  test/schema.test.ts [ test/schema.test.ts ]
Error: Dynamic require of "fs" is not supported
 ❯ node_modules/drizzle-kit/api.mjs:12:9
 ❯ ../node_modules/.pnpm/fs.realpath@1.0.0/node_modules/fs.realpath/index.js node_modules/drizzle-kit/api.mjs:768:14
 ❯ __require2 node_modules/drizzle-kit/api.mjs:18:50
 ❯ ../node_modules/.pnpm/glob@8.1.0/node_modules/glob/glob.js node_modules/drizzle-kit/api.mjs:2324:14
 ❯ __require2 node_modules/drizzle-kit/api.mjs:18:50
 ❯ src/serializer/index.ts node_modules/drizzle-kit/api.mjs:9567:20

Why the ESM build of the API cannot call require#

drizzle-kit ships its programmatic API twice. The package's exports map sends import to api.mjs and require to api.js. Both are bundles: the dependencies, including the CommonJS packages glob and fs.realpath, are copied into one file. When a CommonJS dependency inside an ES module bundle calls require("fs"), the bundler rewrites it to a helper. The helper is at the top of api.mjs in 0.31.5, and a commenter on the issue quoted the same code:

javascript
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
  if (typeof require !== "undefined") return require.apply(this, arguments);
  throw Error('Dynamic require of "' + x + '" is not supported');
});

If a require function is in scope, the helper uses it. In a CommonJS file there always is one. In an ES module there is not, so the helper reaches its last line and throws, naming the first module it was asked for: fs, requested by the bundled copy of fs.realpath. The stack shows why the import alone is enough: loading the API runs the initialisation of src/cli/commands/migrate.ts, which initialises the serializer, which loads glob, which loads fs.realpath. That is also why index.cjs works: it resolves to api.js, where Node provides require. In the reproduction, grep -c createRequire on the 0.31.5 api.mjs printed 0: nothing in that file defines a require.

The first comment on the issue came from the reporter, a few hours after filing. Adding two lines that create a require with Node's module.createRequire() to the top of node_modules/drizzle-kit/api.mjs made the import work, and the reporter suggested adding them as a banner in the build. That is what the fix did. PR #4999, merged on 28 October 2025 as commit ad4ddd4, moved the API to its own tsup build in drizzle-kit/build.ts. For the ESM format only, it prepends this line, with a comment that links issue #2853:

javascript
import { createRequire } from 'module'; const require = createRequire(import.meta.url);

Now typeof require !== "undefined" is true inside api.mjs, and the helper hands fs to a real require. In the reproduction, that line was the first line of api.mjs after the upgrade to 0.31.6. The registry shows 0.31.6 was published the same day the PR was merged.

Fix#

  1. Check which drizzle-kit you actually have. On the stable 0.x line, a version below 0.31.6 is affected. The 1.0.0 beta builds are a separate line: there, a maintainer wrote in the thread "Fixed in drizzle-kit@1.0.0-beta.10-4a43a22" in reply to a regression report about Bun, so an older beta build may still fail. The betas were not tested here.
bash
npx drizzle-kit --version
  1. Upgrade drizzle-kit. On 30 October 2026 the npm latest tag pointed at 0.31.11, which imported cleanly in the reproduction:
bash
npm install -D drizzle-kit@latest
  1. If you cannot upgrade yet, load the CommonJS build from your ES module. This is the workaround from the issue thread, shown here in TypeScript as it was posted. It keeps the change in your own code instead of patching node_modules:
typescript
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)
const { generateDrizzleJson, generateMigration } = require('drizzle-kit/api') as typeof import('drizzle-kit/api')

In plain JavaScript on 0.31.5, the reproduction ran this file and printed the migration SQL, while the same imports through import failed:

javascript
// workaround.mjs
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
const { generateDrizzleJson, generateMigration } = require("drizzle-kit/api");
 
import { pgTable, serial, text } from "drizzle-orm/pg-core";
const users = pgTable("users", { id: serial("id").primaryKey(), name: text("name") });
 
const prev = generateDrizzleJson({});
const next = generateDrizzleJson({ users });
console.log(await generateMigration(prev, next));

Remove this once you are on 0.31.6 or later, and go back to a normal import.

Verify the fix#

Run the file that failed again, with nothing else changed. In the reproduction the only change was drizzle-kit 0.31.5 to 0.31.6:

bash
npm install -D -E drizzle-kit@0.31.6
node index.mjs
text
drizzle-kit API imported

To check that the API works and not just that it loads, call a function that does not need a database. This generate.mjs failed on 0.31.5 and, on 0.31.6 and 0.31.11, printed a CREATE TABLE statement:

javascript
// generate.mjs
import { generateDrizzleJson, generateMigration } from "drizzle-kit/api";
import { pgTable, serial, text } from "drizzle-orm/pg-core";
 
const users = pgTable("users", { id: serial("id").primaryKey(), name: text("name") });
 
const prev = generateDrizzleJson({});
const next = generateDrizzleJson({ users });
console.log(await generateMigration(prev, next));
text
[
  'CREATE TABLE "users" (\n\t"id" serial PRIMARY KEY NOT NULL,\n\t"name" text\n);\n'
]

The Vitest suite that failed on 0.31.5 reported Test Files 1 passed (1) on 0.31.6 and on 0.31.11. If you want to confirm the fix is in your install rather than trusting the version number, print the first line of the ESM build. It should be the createRequire banner:

bash
head -1 node_modules/drizzle-kit/api.mjs

pushSchema itself was not run against a database in this reproduction. It lives in the same bundle and failed on the same import line, so the import part of the fix applies to it too.

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.

await import() and test runners do not get around it#

Switching to a dynamic import looks like an escape hatch, but it is not one. await import("drizzle-kit/api") resolves to the same api.mjs through the same import condition and runs the same initialisation code. On 0.31.5 the reproduction's dynamic.mjs threw the same Dynamic require of "fs" error. On 0.31.6 it printed function.

Test runners do not change the outcome either. Commenters on the issue reached it from integration tests that push a schema into a throwaway database, one of them in a Vitest suite with testcontainers. In the reproduction, Vitest 5.0.5 failed the suite with the same error. The upgrade fixed it there too: the same suite passed on 0.31.6 and 0.31.11. The createRequire workaround was tested only with plain Node, not inside Vitest.

This is a different bug from the drizzle-kit CLI failing to load your schema. If drizzle-kit generate or push stops on SyntaxError: Cannot use import statement outside a module, the CLI's old CommonJS loader is the cause, and the page on drizzle-kit and Cannot use import statement outside a module covers it; the drizzle-kit 0.31.10 release notes describe that loader change. For how Node decides which files are ES modules and which are CommonJS, through "type", the file extension and syntax detection, see exports is not defined in ES module scope.

The next error: drizzle-kit/api on the 1.0 release candidate#

The 1.0 line has not reached latest, but the npm rc tag pointed at 1.0.0-rc.5 on 30 October 2026, and its exports map has no ./api entry. It lists ./api-postgres, ./api-mysql and ./api-sqlite instead. In the reproduction, with drizzle-kit and drizzle-orm 1.0.0-rc.5, the reporter's index.mjs no longer reached the fs error. Node refused the path first, with Package subpath './api' is not defined by "exports" followed by the path of drizzle-kit's package.json, from its resolver:

text
node:internal/modules/esm/resolve:315
  return new ERR_PACKAGE_PATH_NOT_EXPORTED(
         ^

Importing drizzle-kit/api-postgres from the same ES module loaded and listed generateDrizzleJson, generateMigration, pushSchema, reconstruct, startStudioServer and up. Only the export names were checked there, and no function was called. If you move a PostgreSQL project to the release candidate, change the import path to the dialect subpath. For the general rule behind that message, which is that a subpath must be listed in the package's exports map, see the ERR_PACKAGE_PATH_NOT_EXPORTED page.

On the stable line, the whole fix is one version bump. The error comes from a missing require inside drizzle-kit's own ES module bundle, not from your code, and a recent Node does not make it go away: it reproduced on Node v26.7.0. Once head -1 shows the createRequire banner, pushSchema, generateDrizzleJson and generateMigration import from ES modules and Vitest suites the same way they always did from CommonJS.

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