drizzle-kit generate: Cannot find module .js under nodenext
Get drizzle-kit generate working in a "type": "module" project that writes .js extensions in its TypeScript imports, without dropping nodenext.
TL;DR#
In a TypeScript project with "module": "nodenext" and "type": "module", schema files import each other with a .js extension, as TypeScript requires: import { users } from './users.js'. drizzle-kit 0.31.9 and older load the schema with a CommonJS require hook that does not map ./users.js to users.ts, so drizzle-kit generate stops with Error: Cannot find module './users.js'. Upgrade drizzle-kit to 0.31.10 or later, where the loader is tsx: in a reproduction, that single change made generate succeed. If you cannot upgrade, run the old CLI under tsx, or point schema at the files tsc emits.
The error from drizzle-kit 0.31.9#
The report is drizzle-orm issue #3348, opened in June 2024 against drizzle-kit 0.22.7 and still open. Commenters hit it again on later releases, with "moduleResolution": "Node16" as well as nodenext. The same failure reproduces on drizzle-kit 0.31.9, the last release before the loader changed, with a two-file schema:
// src/db/schema/posts.ts
import { integer, pgTable, serial, text } from 'drizzle-orm/pg-core';
import { users } from './users.js';
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
authorId: integer('author_id').notNull().references(() => users.id),
});tsc accepts the project, and drizzle-kit generate fails on the second line of posts.ts:
$ npx tsc --noEmit
tsc exit=0
$ npx drizzle-kit generate # drizzle-kit 0.31.9
No config path provided, using default 'drizzle.config.ts'
Reading config file '/app/drizzle.config.ts'
Error: Cannot find module './users.js'
Require stack:
- /app/src/db/schema/posts.ts
- /app/node_modules/drizzle-kit/bin.cjs
at Function.<anonymous> (node:internal/modules/cjs/loader:1430:15)
at Module._resolveFilename (/app/node_modules/drizzle-kit/bin.cjs:16806:40)
code: 'MODULE_NOT_FOUND',Two lines tell you which case you are in. The require stack ends in drizzle-kit/bin.cjs, so the import is being resolved by drizzle-kit's CommonJS loader, not by Node's ES module loader. And the file it cannot find is a .js name that exists on disk only as a .ts file.
This was reproduced on 22 October 2026 in the official node:22-bookworm-slim Docker image (Node.js 22.23.3), with drizzle-orm 0.45.4 and TypeScript 7.0.2, in a project with "type": "module" and this tsconfig.json:
{
"compilerOptions": {
"target": "es2022",
"module": "nodenext",
"moduleResolution": "nodenext",
"strict": true,
"skipLibCheck": true,
"rootDir": "src",
"outDir": "dist"
},
"include": ["src"]
}Why the .js specifier never reaches users.ts#
Under nodenext, TypeScript models how Node.js will run the compiled output. Node's ES module loader does not guess extensions, so a relative import must name the file that will exist after compilation: ./users.js, not ./users and not ./users.ts. That is why the import looks wrong for a .ts source and is still correct.
The drizzle-kit generate docs explain that the command reads through your Drizzle schema files to build a JSON snapshot, so it has to execute your TypeScript. Up to 0.31.9, the bundled bin.cjs does that with esbuild-register, a CommonJS require hook. Its patch of Node's resolver, visible in the installed 0.31.9 bin.cjs, only handles tsconfig path aliases:
Module._resolveFilename = function(request, _parent) {
const isCoreModule = _module2.builtinModules.includes(request);
if (!isCoreModule) {
const found = matchPath(request);
if (found) {
const modifiedArguments = [found, ...[].slice.call(arguments, 1)];
return originalResolveFilename.apply(this, modifiedArguments);
}
}
return originalResolveFilename.apply(this, arguments);
};./users.js is not an alias, so it falls through to Node's own CommonJS resolver, which looks for src/db/schema/users.js, does not try users.ts in its place, and throws MODULE_NOT_FOUND. The issue author's temporary patch adds exactly that missing step: when a request ends in .js and a .ts file with the same name exists, resolve that instead.
The drizzle-kit 0.31.10 release notes say the CLI migrated away from esbuild-register to the tsx loader, so that it works with both ESM and CJS modules. The notes do not mention issue #3348, and the issue is still open. What the reproduction shows is that the tsx loader resolves ./users.js to users.ts where the old hook did not.
Fix#
1. Upgrade drizzle-kit#
Change nothing but the drizzle-kit version. Pin the 0.31 line rather than moving to the beta or rc tags: those carry the drizzle-kit rewrite that the maintainers' status comment on the issue describes, with a new migration folder structure, and that is a separate upgrade from this fix.
npm install -D drizzle-kit@^0.31.11
npm ls drizzle-kitOn the reproduction project, moving from 0.31.9 to 0.31.10 with no other change produced the migration:
$ npx drizzle-kit generate # drizzle-kit 0.31.10
No config path provided, using default 'drizzle.config.ts'
Reading config file '/app/drizzle.config.ts'
2 tables
posts 3 columns 0 indexes 1 fks
users 2 columns 0 indexes 0 fksThe current release at the time of writing, 0.31.11, gave the same result. The 1.0 beta line depends on jiti rather than tsx, and was not tested here.
2. If you are pinned to 0.31.9 or older, run the CLI under tsx#
A commenter on the issue suggested starting the CLI's bin.cjs through tsx instead of through npx drizzle-kit. drizzle-kit 0.31.9 does not depend on tsx, so add it first:
npm install -D tsx
npx tsx node_modules/drizzle-kit/bin.cjs generateWith drizzle-kit 0.31.9 and tsx 4.23.15, the same schema generated its migration:
$ npx tsx node_modules/drizzle-kit/bin.cjs generate # 0.31.9 under tsx
No config path provided, using default 'drizzle.config.ts'
Reading config file '/app/drizzle.config.ts'
2 tables
posts 3 columns 0 indexes 1 fks
users 2 columns 0 indexes 0 fksPut it in a script so nobody types the long path:
{
"scripts": {
"db:generate": "tsx node_modules/drizzle-kit/bin.cjs generate"
}
}3. Or point schema at the compiled JavaScript#
Another workaround from the thread: let tsc emit the schema, then give drizzle-kit the emitted .js files, where ./users.js really exists:
// drizzle.config.ts
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
dialect: 'postgresql',
schema: './dist/db/schema/*.js',
out: './drizzle',
});npx tsc
npx drizzle-kit generateThis works on 0.31.9 too, but it only sees the schema as of the last build. Chain the two commands in one script, or a generate run after editing schema.ts without rebuilding compares against stale code.
Verify the fix#
Run generate and read the output, not only the exit code. On drizzle-kit 0.31.9, the failed run above still returned success to the shell:
$ npx drizzle-kit generate > out.txt 2>&1; echo "exit=$?" # 0.31.9
exit=0So a CI job on 0.31.9 can stay green while producing no migration. After the fix, the output lists your tables and names the new SQL file, and the drizzle folder contains it. For the two-file schema, the foreign key from posts to users, the import that used to fail, now appears in the SQL:
ALTER TABLE "posts" ADD CONSTRAINT "posts_author_id_users_id_fk" FOREIGN KEY ("author_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;If you added a script that runs generate in CI, also check that the expected .sql file exists, at least until you have confirmed how your drizzle-kit version reports a schema that fails to load.
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.
Dropping the .js extension trades one error for another#
The tempting shortcut is to write import { users } from './users' so the old loader finds the file. Under "module": "nodenext", tsc refuses that import with TS2835 and asks for the .js name, and plain Node.js cannot load the compiled output without it either. The page on ERR_MODULE_NOT_FOUND for a relative import without .js walks through that error and the matching TypeScript setting. The same strict rule also forbids importing a folder; if your schema has import * as schema from './schema' pointing at a directory, see Directory import is not supported.
One commenter moved the whole project to "moduleResolution": "bundler" and adds extensions at build time with a separate tool. That makes drizzle-kit happy at the cost of changing how every file in the project compiles, which is a larger decision than this error.
After generate works: what fails next#
The old loader has a sibling failure on 0.31.9 and older, but only on a Node.js release that cannot require() ES modules: a schema or config that pulls in an ES module package stops with SyntaxError: Cannot use import statement outside a module. On a Node.js release that can, Node loads such a package itself and that error does not appear; the linked page lists which releases those are. If that is what you see instead of this error, the page on drizzle-kit and Cannot use import statement outside a module covers it. Once generate works, drizzle-kit migrate is the next step; if it prints Using 'pg' driver for database querying and then nothing, read drizzle-kit migrate hanging after the pg driver line, which also notes that since 0.31.10 a failed migrate exits with code 1. And if your tables live in a custom pgSchema and db.query then answers relation "user" does not exist, Drizzle db.query dropping pgSchema covers the drizzle-orm versions that lose the schema name.
The .js import in a .ts file is correct TypeScript for Node.js. The failure was drizzle-kit's older loader not following it, so the durable fix is the newer drizzle-kit, and the two workarounds are for projects that cannot move yet.
Further reading: drizzle-kit/api: Dynamic require of "fs" is not supported.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
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.
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.
Fix ESLint: Module.createRequire is not a function
Reproduced on Node 10 vs Node 18 with the same node_modules: the crash is the Node runtime executing ESLint, not Parcel or a broken module resolution.


