Prisma Schema to SQL
Paste a schema.prisma and get runnable CREATE TABLE DDL — columns with Prisma's own type mapping, PRIMARY KEY and index statements, enums, and foreign keys with ON DELETE/ON UPDATE actions. PostgreSQL, MySQL, SQLite and SQL Server. Nothing is executed; it all runs in your browser.
About this tool
This tool reads a Prisma schema.prisma and writes the CREATE TABLE DDL
that would build the same database. Paste your models, pick a dialect, and you
get a script you can hand to psql, mysql, sqlite3 or sqlcmd — or paste
into a migration file, a docker-entrypoint seed script, or a code review to show
exactly what a schema change does at the SQL level.
Nothing is executed and no database is contacted: the schema is parsed and mapped inside your browser, and the result is text.
What each part of the schema becomes
modelblocks →CREATE TABLE. Every scalar field becomes a column, typed the way Prisma's own migrations type it for the chosen provider. Required fields getNOT NULL; relation fields get no column of their own.@id/@@id→PRIMARY KEY. A single-field key and a composite@@id([a, b])both work; on PostgreSQL and SQL Server the constraint is namedTable_pkey, matching Prisma's naming.@default(...)→DEFAULT. Literals,now(), anddbgenerated("…")become SQL defaults.autoincrement()becomesSERIAL/BIGSERIAL,AUTO_INCREMENT,PRIMARY KEY AUTOINCREMENTorIDENTITY(1,1)depending on the dialect.@unique/@@unique/@@index→ index statements, named the way Prisma names them (Table_column_key,Table_column_idx) unless the attribute carries its ownmap:name.enumblocks → aCREATE TYPE … AS ENUMon PostgreSQL, an inlineENUM('…')column on MySQL, and aCHECK (col IN ('…'))constraint on SQLite and SQL Server, which have no enum type of their own.@relation(fields: […], references: […])→ foreign keys, emitted asALTER TABLE … ADD CONSTRAINT … FOREIGN KEYafter all the tables exist, so creation order never matters.onDelete/onUpdatemap toON DELETE/ON UPDATEactions; when they are absent, Prisma's own defaults apply —RESTRICTfor a required relation,SET NULLfor an optional one, andCASCADEon update.- Implicit many-to-many relations (a list on both sides, no
fields:) get their_AToBjoin table, with theA/Bcolumns typed from each side's primary key, the composite primary key, theBindex and both cascading foreign keys. @map/@@map→ renamed columns and tables, and the new names are used everywhere downstream — indexes, primary keys and foreign keys all follow.@db.*native types are used verbatim, so@db.VarChar(200)becomesVARCHAR(200)and@db.Timestamptz(6)becomesTIMESTAMPTZ(6).
Type mapping
| Prisma | PostgreSQL | MySQL | SQLite | SQL Server |
|---|---|---|---|---|
String | TEXT | VARCHAR(191) | TEXT | NVARCHAR(1000) |
Boolean | BOOLEAN | TINYINT(1) | BOOLEAN | BIT |
Int | INTEGER | INT | INTEGER | INT |
BigInt | BIGINT | BIGINT | BIGINT | BIGINT |
Float | DOUBLE PRECISION | DOUBLE | REAL | FLOAT(53) |
Decimal | DECIMAL(65,30) | DECIMAL(65,30) | DECIMAL | DECIMAL(32,16) |
DateTime | TIMESTAMP(3) | DATETIME(3) | DATETIME | DATETIME2 |
Json | JSONB | JSON | JSONB | not supported |
Bytes | BYTEA | LONGBLOB | BLOB | VARBINARY(MAX) |
A @db.* attribute always wins over the row above.
The toggles
SQL dialect defaults to Auto, which reads the datasource block's
provider and falls back to PostgreSQL when the schema has no datasource —
handy when you paste only the models. Emit foreign-key constraints and
Emit index statements let you keep just the bare tables, which is what you
usually want when seeding a scratch database and loading data before the
constraints go on. Add IF NOT EXISTS guards makes the script re-runnable,
and Prepend DROP TABLE IF EXISTS makes it rebuild from zero — that one is
destructive, so keep it for throwaway databases. Quote identifiers is on by
default because Prisma's camelCase table and column names need quoting to
survive; turn it off for a lowercase, folded-identifier script.
Worked example
Schema:
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
}
PostgreSQL output:
CREATE TABLE "User" (
"id" SERIAL NOT NULL,
"email" TEXT NOT NULL,
"name" TEXT,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
Limits & edge cases
- The schema must be at most 200,000 bytes. Larger input is rejected rather than silently truncated.
- The parser is lenient and structural, not the real Prisma compiler: it
reads
model,view,enum,datasourceandgeneratorblocks and ignores everything it does not model. It will not tell you your schema is invalid — it is a translator, not a validator. Unbalanced braces and unknown field types are the two things it does reject. - A field whose type is neither a Prisma scalar, an
enumin the same schema, nor amodelin the same schema is an error, because there is nothing to map it to. Paste the whole schema, not one model out of it. Jsonhas no SQL Server equivalent here and is reported as an error; store it asString @db.NVarChar(Max)if you need that dialect.- Scalar lists (
String[]) exist only on PostgreSQL, where they become array columns. On the other dialects the field is skipped and a--comment marks the spot. - Client-side ID generators produce no database default.
uuid(),cuid(),ulid(),nanoid()andauto()are filled in by Prisma Client, so the column is emitted without aDEFAULT. Usedbgenerated("gen_random_uuid()")when you want the database to do it. @updatedAtis application behaviour, not a database trigger, so noON UPDATE CURRENT_TIMESTAMPis generated — Prisma's own migrations behave the same way.- Explicit many-to-many models (a join model you wrote yourself) are ordinary
tables and need no special handling; only implicit m-n relations synthesise
a
_AToBtable. - MongoDB schemas are out of scope: there is no
CREATE TABLEfor a document database, and@db.ObjectId/auto()have no SQL meaning. - The output is a from-empty script — it builds a schema, it does not diff one against a live database. For an incremental migration between two schema versions, use Prisma's own migration tooling.
FAQ
Is this the same SQL that Prisma Migrate would generate?
It is very close, and it follows the same type map, the same constraint and
index naming (Table_column_key, Table_column_idx, Table_pkey) and the same
referential-action defaults, so the two line up on ordinary schemas. It is not a
byte-for-byte reimplementation of the migration engine, though: statement
ordering, some provider-specific edge cases and any feature-preview behaviour
can differ. Treat the result as a very good starting script and review it before
running it against anything you care about.
Why does my @default(uuid()) column have no DEFAULT?
Because uuid(), cuid(), ulid(), nanoid() and auto() are generated by
Prisma Client in your application, not by the database — the column genuinely
has no server-side default in a Prisma-managed schema either. If you want the
database to generate the value, express that explicitly with
@default(dbgenerated("gen_random_uuid()")), and the expression is passed
straight through into the DDL.
What happens to my enums on SQLite and SQL Server?
Neither dialect has a native enum type, so the column is emitted as TEXT
(SQLite) or NVARCHAR(1000) (SQL Server) with a CHECK (col IN ('A', 'B'))
constraint that enforces the same set of values. On PostgreSQL you get a real
CREATE TYPE … AS ENUM and the column references it; on MySQL you get an inline
ENUM('A', 'B') column. @map on an enum value is respected everywhere, so the
database sees the mapped string, not the Prisma identifier.
Do I get foreign keys for both sides of a relation?
No — and that is correct. A Prisma relation is written on both models, but only
one side carries fields: and references:, and that is the side that owns the
column and therefore the foreign key. The back-relation field (posts Post[] on
User, say) produces neither a column nor a constraint. All the foreign keys
are emitted as ALTER TABLE statements after every CREATE TABLE, so you can
run the script top to bottom regardless of the order your models are in.
Can I run the script twice, or rebuild a scratch database from zero?
Yes. Add IF NOT EXISTS guards makes every CREATE TABLE (and, on PostgreSQL
and SQLite, every index) skip quietly if the object is already there; on SQL
Server it becomes an IF OBJECT_ID(…) IS NULL guard, since that dialect has no
IF NOT EXISTS on CREATE TABLE. Prepend DROP TABLE IF EXISTS goes further
and drops everything first, in reverse creation order, plus the PostgreSQL enum
types. That second one destroys data, so point it only at a throwaway database.
What if I only paste the models, with no datasource block?
That works. The SQL dialect control defaults to Auto, which looks for a
datasource block and uses its provider; with no datasource to read, it falls
back to PostgreSQL. Pick a dialect explicitly from the dropdown whenever you
want a specific target, and the datasource — if there is one — is ignored.
Developer & Automation Access
Run it from the terminal
Same engine as this page, headless — via the gizza CLI:
gizza tool prisma-schema-to-sql "model User {
id Int @id @default(autoincrement())
email String @unique
name String?
}"New to the CLI? Get gizza →
Open it by URL
Pre-fill and auto-run this tool with query parameters — the names match the API/CLI:
https://gizza.ai/tools/prisma-schema-to-sql/?input=model%20User%20%7B%0A%20%20id%20%20%20%20Int%20%20%20%20%20%40id%20%40default%28autoincrement%28%29%29%0A%20%20email%20String%20%20%40unique%0A%20%20name%20%20String%3F%0A%7D&dialect=auto&foreign_keys=true&indexes=true&if_not_exists=true&drop_if_exists=true"e_identifiers=trueMachine-readable descriptor: tool.json — title + parameters JSON Schema for agents.
