DDL to ORM Models
Convert CREATE TABLE DDL into Prisma schema, SQLAlchemy models or TypeORM entities — with a cited type mapping, entirely on this device.
Paste DDL on the left to generate models for the selected target.
Reads CREATE TABLE and ALTER TABLE … ADD CONSTRAINT — keys, foreign keys and defaults included.
Type mapping — 0 rows
No mapped columns yet.
Press ? for shortcuts · DDL and generated code stay on this device — nothing is uploaded. · Nothing is stored between visits.
Do it offline:$ npx prisma db pull
How to use it
- Paste your DDL — CREATE TABLE plus ALTER TABLE … ADD CONSTRAINT — and pick the dialect it was written for; the code regenerates a moment after you stop typing.
- Switch between the Prisma, SQLAlchemy and TypeORM tabs; the SQLAlchemy tab adds a style control for 2.0 Mapped[] or classic declarative Column() output.
- Open the type-mapping drawer under the code pane to audit every conversion this run used, with its documentation source and a lossy marker where round-trip fidelity breaks.
- Copy the active file or download it under its own name — TypeORM output is one entity file per table, so select the file first.
Paste CREATE TABLE statements and get model code for the ORM you actually use: a Prisma schema, SQLAlchemy 2.0 classes or TypeORM entities. Each type conversion comes from a cited mapping table that marks the exact rows where fidelity breaks, foreign keys become relation fields on both sides, and anything unreadable is skipped out loud. The whole run happens in this tab.
Examples
-- target: prisma
CREATE TABLE customers (
id serial PRIMARY KEY,
email varchar(255) NOT NULL UNIQUE
);
CREATE TABLE orders (
id serial PRIMARY KEY,
customer_id integer NOT NULL REFERENCES customers(id),
total numeric(10,2) NOT NULL,
placed_at timestamptz NOT NULL DEFAULT now()
);// generated from DDL — review relations and defaults
model customers {
id Int @id @default(autoincrement())
email String @unique
orders orders[]
}
model orders {
id Int @id @default(autoincrement())
customer_id Int
total Decimal
placed_at DateTime @default(now())
customers customers @relation(fields: [customer_id], references: [id])
}
The serial primary key arrives as @id @default(autoincrement()), the REFERENCES clause becomes a @relation field on orders AND a list field on customers, and numeric(10, 2) lands as Decimal — with a lossy row in the drawer because the Prisma scalar does not carry precision.
-- target: sqlalchemy
CREATE TABLE customers (
id serial PRIMARY KEY,
email varchar(255) NOT NULL UNIQUE
);
CREATE TABLE orders (
id serial PRIMARY KEY,
customer_id integer NOT NULL REFERENCES customers(id),
total numeric(10,2) NOT NULL,
placed_at timestamptz NOT NULL DEFAULT now()
);# generated from DDL — review relations and defaults
from datetime import datetime
from decimal import Decimal
from sqlalchemy import DateTime, ForeignKey, Integer, Numeric, String, text
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
class Base(DeclarativeBase):
pass
class Customers(Base):
__tablename__ = "customers"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
email: Mapped[str] = mapped_column(String(255), unique=True)
orders: Mapped[list["Orders"]] = relationship(back_populates="customers")
class Orders(Base):
__tablename__ = "orders"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
customer_id: Mapped[int] = mapped_column(Integer, ForeignKey("customers.id"))
total: Mapped[Decimal] = mapped_column(Numeric(10, 2))
placed_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=text("now()"))
customers: Mapped["Customers"] = relationship(back_populates="orders")
The same DDL in 2.0 style: Mapped[] annotations drive nullability, the foreign key becomes ForeignKey("customers.id") plus a relationship() pair wired with back_populates, Numeric(10, 2) keeps its precision, and DEFAULT now() rides as server_default=text("now()").
-- target: typeorm
CREATE TABLE customers (
id serial PRIMARY KEY,
email varchar(255) NOT NULL UNIQUE
);
CREATE TABLE orders (
id serial PRIMARY KEY,
customer_id integer NOT NULL REFERENCES customers(id),
total numeric(10,2) NOT NULL,
placed_at timestamptz NOT NULL DEFAULT now()
);// generated from DDL — review relations and defaults
import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { Customers } from "./customers.entity";
@Entity("orders")
export class Orders {
@PrimaryGeneratedColumn()
id!: number;
@Column("int")
customer_id!: number;
@Column("numeric", { precision: 10, scale: 2 })
total!: string;
@Column("timestamptz", { default: () => "now()" })
placed_at!: Date;
@ManyToOne(() => Customers, (customers) => customers.orders)
@JoinColumn({ name: "customer_id", referencedColumnName: "id" })
customers!: Customers;
}
TypeORM emits one file per table — this is orders.entity.ts, which imports the Customers class from its sibling. Note total!: string: TypeORM returns numeric columns as strings, so the property type says so instead of pretending it is a number.
CREATE TABLE hits (id bigint PRIMARY KEY, views int unsigned NOT NULL, day date NOT NULL);// generated from DDL — review relations and defaults
model hits {
id BigInt @id
views Int
day DateTime
}
MySQL edge case: int unsigned still generates Int and date still generates DateTime, but neither silently — the drawer marks both rows lossy, one because Int is signed, one because a date-only value gains an invented midnight.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
A composite primary key emits differently in every target
PRIMARY KEY (user_id, role) becomes @@id([user_id, role]) in Prisma, primary_key=True on each mapped_column in SQLAlchemy, and one @PrimaryColumn() per member in TypeORM. Key members are emitted non-nullable everywhere, even when the DDL omitted NOT NULL, because SQL implies it.
Renames are bookkept, never silent
A column named class is a Python keyword, so the SQLAlchemy attribute becomes class_ while mapped_column("class", …) keeps the real column. Prisma gets @map, TypeORM gets the name option in @Column, and every rename is listed as a W_NAME_COLLISION diagnostic under the pane.
Lossy rows are marked, not hidden
Some conversions cannot round-trip: UNSIGNED integers land in signed types, varchar lengths and numeric precision vanish into Prisma scalars, DATE and TIME widen to DateTime. The mapping drawer tags each such row with the reason, so you know what to add back by hand before trusting the schema.
Relation direction follows the foreign key
The table holding the constraint gets the to-one field; the referenced table gets the list. A join table therefore stays two one-to-manys — nothing invents a many-to-many for you. A foreign key whose target table is missing from the paste keeps its plain column, and the omitted relation is reported.
Common errors
No CREATE TABLE statements found in this input. This looks like DML (SELECT/INSERT/UPDATE/DELETE) — paste schema DDL instead, e.g. the output of pg_dump --schema-only.- Queries describe questions, not tables — there is nothing to model. The hint names the dump command that produces the right input.
Column "moods"."aura" has type "citext", which has no Prisma mapping — a commented placeholder was emitted, nothing was coerced.- Extension and vendor types outside the mapping table become a commented line in the model, so the generated file stays honest about what it dropped instead of guessing a type.
Table "audit_log" has no primary key or unique column — Prisma cannot address its rows, so the model is emitted with @@ignore.- Every target needs a way to address a row. Prisma marks the model @@ignore; SQLAlchemy and TypeORM output the class fully commented out, since both require a primary key to map at all.
FAQ
Is this the same as running prisma db pull?
No. Introspection connects to a live database and reads real column metadata; this page reads only the DDL text you paste, so anything your dump omits stays unknown. The npx prisma db pull line on this page is the doc-cited introspection route — printed for when you have a connection string, never executed here.
Which statements are read, and what happens to the rest?
CREATE TABLE (inline and table-level PRIMARY KEY, FOREIGN KEY, UNIQUE, REFERENCES) and ALTER TABLE … ADD CONSTRAINT, in the postgresql, mysql, sqlite, tsql and ansi dialects, up to 512 KiB and 120 tables. Views, indexes, procedures and CHECK bodies are counted as skipped and listed — never silently dropped.
Does my schema leave this device?
No — parsing and generation are JavaScript running in this tab, and nothing is stored between visits. Share links carry state in the URL fragment itself, which is why sharing is an explicit button, and a credential-shaped paste prompts before any link is made.
Keyboard shortcuts
- /
- Put the cursor in the DDL pane.
- ⌘/Ctrl + S
- Download the file that is showing, under its generated name.
- ⌘/Ctrl + C
- Copy the visible file when no text is selected; selections copy normally.
- ⌘/Ctrl + Shift + C
- Copy the visible file even while a selection exists.
- t
- Switch the theme; skipped while a field has focus.
- ? or ⌘/Ctrl + /
- Open this shortcut list in the help dialog.
- Esc
- Dismiss the topmost dialog; pane contents stay put.