@flixydev/prisma-to-drizzle-v1-generator
v0.0.9
Published
Prisma 6 generator that emits Drizzle ORM v1-rc PostgreSQL schema and relations.
Maintainers
Readme
Prisma to Drizzle V1 Generator
Generate Drizzle ORM 1.0 RC PostgreSQL schema files from a Prisma ^6 schema.
The generator is built for teams that want to move runtime code from Prisma to Drizzle without letting Drizzle migrations invent a different database. It fails closed: if a Prisma feature cannot be mapped to migration-equivalent Drizzle output, generation stops with a diagnostic instead of emitting an approximation.
Status
- Target database: PostgreSQL.
- Target Prisma schema: Prisma
^6. - Target Drizzle ORM:
1.0.0-rc. - Main output: Drizzle table definitions, enums, foreign keys, indexes, primary keys, unique indexes, and Drizzle V1 relations.
- Other databases are intentionally not supported yet.
Install
pnpm add drizzle-orm pg
pnpm add -D @flixydev/prisma-to-drizzle-v1-generator drizzle-kit prismadrizzle-orm is a peer dependency. pg is not required by this generator
itself, but Drizzle Kit needs a PostgreSQL driver for pull, migrate, and
similar database commands. You may use another Drizzle-supported PostgreSQL
driver if your project already has one.
Add the Prisma generator
generator drizzle {
provider = "prisma-to-drizzle-v1-generator"
output = "../src/db/generated"
dialect = "postgresql"
importSpecifiers = "js"
format = true
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}Then run:
pnpm prisma generateThe generated folder is flat:
src/db/generated/
index.ts
schema.ts
relations.ts
users.ts
posts.ts
user-role-enum.tsrelations.ts is the Drizzle connection entrypoint:
import { drizzle } from "drizzle-orm/node-postgres";
import { relations } from "./db/generated/relations.js";
export const db = drizzle(client, { relations });index.ts is the application barrel and exports only generated tables and
enums:
import { users, userRoleEnum } from "./db/generated/index.js";For Drizzle Kit CLI, pass the generated directory, not only schema.ts:
// drizzle.config.ts
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "postgresql",
schema: "./src/db/generated",
out: "./drizzle",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});Drizzle Kit 1.0.0-rc.1 scans exported tables from schema files. The aggregate
schema object exported by schema.ts is an internal helper for generated
relations and tooling, not the Drizzle connection config.
Configuration
type GeneratorConfig = {
output: string;
dialect?: "postgresql";
importSpecifiers?: "extensionless" | "js" | "ts";
format?: boolean;
};output: required output directory.dialect: onlypostgresqlis supported.importSpecifiers: generated relative import style.format: runsoxfmton generated files whentrue.
Import specifier modes:
extensionless -> import { users } from "./users";
js -> import { users } from "./users.js";
ts -> import { users } from "./users.ts";Use js for NodeNext/Node ESM TypeScript projects that compile to JavaScript.
Use extensionless for bundler-style resolution. Use ts when a tool reads the
generated TypeScript files directly.
CLI
The package also exposes a debug CLI:
prisma-to-drizzle-v1 generate \
--schema prisma/schema.prisma \
--output src/db/generated \
--dialect postgresql \
--import-specifiers js \
--format true--schema accepts either one schema.prisma file or a Prisma schema directory.
DMMF audit helper:
prisma-to-drizzle-v1 audit-dmmf --schema prismaMigration handoff from Prisma to Drizzle
There are two separate steps:
- Generate Drizzle runtime schema from Prisma.
- Baseline Drizzle Kit migrations against the existing database.
Do not apply a first Drizzle migration generated from an empty migration history to an existing Prisma database. That migration would describe creating the whole database from scratch.
Recommended handoff:
# 1. Generate Drizzle schema files.
pnpm prisma generate
# 2. Create Drizzle Kit baseline metadata from the existing DB.
pnpm drizzle-kit pull --init --config drizzle.config.ts
# 3. Verify that the generated schema produces no follow-up migration.
pnpm drizzle-kit generate --config drizzle.config.tsExpected result for step 3: no schema changes. If Drizzle Kit generates a
migration after pull --init, treat that as a migration fidelity bug to
investigate before switching production migration ownership.
Directives
Directives are written in Prisma documentation comments with the drizzle.
namespace.
Unknown or malformed drizzle.* directives are hard errors.
Runtime defaults
Prisma runtime defaults such as cuid() are application behavior, not database
DDL. Add an explicit runtime default function:
model User {
/// drizzle.defaultFn @paralleldrive/cuid2::createId
id String @id @default(cuid())
}Emits:
id: text("id")
.notNull()
.$defaultFn(() => createId());For compatibility, drizzle.default module::export is also accepted for this
runtime-default use case. Prefer drizzle.defaultFn in new schemas.
Database defaults
Use drizzle.default to explain an existing Prisma @default when the mapping
cannot be inferred safely:
model Token {
/// drizzle.default sql`gen_random_uuid()`
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
}drizzle.default may only explain an existing Prisma @default; it cannot add
a new default that Prisma does not have.
Known defaults are mapped automatically:
now()->sql.raw("CURRENT_TIMESTAMP")autoincrement()->serial,smallserial, orbigserial- scalar and enum literals
- PostgreSQL array defaults
dbgenerated("...")where Prisma exposes the SQL expression
Typed JSON and JSON arrays
model StorePageUrgency {
/// drizzle.defaultFn @paralleldrive/cuid2::createId
id String @id @default(cuid())
/// drizzle.type @flixydev/flixy-types/prisma::StorePageUrgency
urgency Json
}Emits:
import { createId } from "@paralleldrive/cuid2";
import type { StorePageUrgency } from "@flixydev/flixy-types/prisma";
export const storePageUrgencies = pgTable("StorePageUrgency", {
id: text("id")
.notNull()
.$defaultFn(() => createId()),
urgency: jsonb("urgency").$type<StorePageUrgency>().notNull(),
});For Prisma list fields, drizzle.type describes the element type before
Drizzle .array() is applied:
model Page {
/// drizzle.type @app/types::Block
blocks Json[]
}Emits:
blocks: jsonb("blocks").$type<Block>().array();If you want a PostgreSQL array whose element is itself an array, put the array in the type:
/// drizzle.type @app/types::Block[]
nestedBlocks Json[]Custom column builders
Use drizzle.column when Prisma uses an unsupported database type or when your
project has a custom Drizzle column builder:
model Place {
/// drizzle.column @app/db/columns::geometryPoint
/// drizzle.type @app/types::GeoPoint
location Unsupported("geometry(Point,4326)")
}The generator still applies Prisma nullability, defaults, primary keys, unique indexes, and foreign keys where valid.
Custom @updatedAt
Prisma @updatedAt emits:
.$onUpdate(() => new Date())Override it when your runtime needs a different expression:
model Post {
/// drizzle.onUpdateFn () => new Date()
updatedAt DateTime @updatedAt
}Table symbol override
The exported TypeScript table symbol is plural-ish by default. Override it when needed:
/// drizzle.tableSymbol people
model Person {
id String @id
@@map("people")
}SQL table names still come from Prisma @@map or the Prisma model name.
What is generated
The generator currently supports:
- tables and columns
- nullability
- Prisma
@mapand@@map - PostgreSQL native scalar types in the supported matrix
- enums via
pgEnum - primary keys, including compound primary keys
- Prisma
@uniqueand@@uniqueas PostgreSQL unique indexes - non-unique
@@index, including mapped names, sort direction, and GIN indexes - foreign keys and referential actions
- Prisma implicit many-to-many join tables
- Drizzle V1
defineRelations(...) - strongly typed
JsonandJson[] - multi-file Prisma schema input
Important fidelity details:
@default(now())emitsCURRENT_TIMESTAMP, not.defaultNow(), so Drizzle Kit baseline introspection does not create a handoff diff.sort: Descemits.desc().nullsFirst()because PostgreSQL's default null ordering for descending index columns isNULLS FIRST.- Prisma scalar lists are emitted as PostgreSQL arrays without
.notNull(), matching Prisma Migrate's nullable array DDL. - Implicit many-to-many relations generate Prisma's hidden join table shape,
including
_RelationName,A,B, composite primary key, foreign keys, andBindex.
Unsupported features
These are hard errors:
- non-PostgreSQL datasources
relationMode = "prisma"orreferentialIntegrity = "prisma"- PostgreSQL multi-schema output
- views
@ignoreand@@ignore- implicit self many-to-many relations
Unsupported("...")withoutdrizzle.column- PostgreSQL native types outside the supported matrix unless a custom builder is supplied
- runtime defaults such as
cuid()oruuid()withoutdrizzle.defaultFn
File ownership
Generated files contain this header:
// This file was generated by prisma-to-drizzle-v1-generator. Do not edit manually.The generator overwrites files in its configured output directory. It does not write a manifest, metadata file, or metadata directory.
Obsolete generated files are not deleted automatically when models are removed or renamed. Keep the output directory in git and review generated diffs. Legacy manifest artifacts from earlier generator versions are cleaned up when they can be identified as generator-owned.
Testing a migration handoff
This repository includes an end-to-end handoff test:
pnpm run test:postgresIt uses Testcontainers to start postgres:16-alpine, applies Prisma SQL from an
empty database, runs drizzle-kit pull --init, and verifies that
drizzle-kit generate creates no additional migration snapshot.
Package installation can be smoke-tested with:
pnpm run test:packageThat test builds the package, runs pnpm pack, installs the packed tarball into
a temporary consumer project, runs prisma generate through the package
provider, and typechecks the generated schema.
The real Flixy multi-file fixture can be checked with:
pnpm run test:real-worldThat test compares the PostgreSQL SQL footprint from Prisma migrate diff
against the SQL footprint produced from the generated Drizzle schema.
Requirements:
- Docker, OrbStack, or another Testcontainers-compatible container runtime.
- A shell with access to the container runtime socket.
You can also run the same test against an existing PostgreSQL server:
P2D_POSTGRES_URL=postgresql://postgres:postgres@localhost:5432/postgres \
pnpm exec vitest run test/integration/drizzle-baseline-handoff.test.tsIn that mode the test creates and drops a temporary database under the provided server.
Development
pnpm install
pnpm run typecheck
pnpm exec vitest run
pnpm run test:package
pnpm run test:postgres
pnpm run test:real-world
pnpm run format:check
pnpm buildpnpm exec vitest run skips the live PostgreSQL handoff test by default. Use
pnpm run test:postgres when Docker is available.
