What is JSON to Drizzle ORM Schema Generator?
Drizzle ORM is a TypeScript-first, SQL-close ORM where your schema is a plain TypeScript file, with no separate SDL and no separate schema.prisma. You call builder functions like pgTable(), integer(), varchar(), and jsonb() to describe each column, and Drizzle infers TypeScript types directly from those definitions using $inferSelect and $inferInsert. This tool reads a sample JSON object and produces a ready-to-use Drizzle table definition. It infers the most appropriate column type for each field, adds .notNull() where the value is not null, detects id fields and makes them serial().primaryKey(), spots ISO 8601 dates and maps them to timestamp(), and optionally converts camelCase property names to snake_case SQL column names (the Drizzle convention). The most important limitation to understand: Drizzle infers types from a single sample. A field that is populated in your sample will not get nullable treatment. If that field can be null in production, remove .notNull() manually. For the same reason, paste a sample that is representative of your actual data, including any fields that might be missing or null.
How to Use
- Enter the PascalCase table name (e.g. "Post", "OrderItem"). Drizzle conventionally names the exported variable in camelCase plural (e.g. "posts", "orderItems")
- Pick your database provider. This affects which import package is used (drizzle-orm/pg-core, mysql-core, or sqlite-core) and which column builders are available
- Paste a representative JSON object. Include fields that might be null in real data by setting them to null in the sample
- Click Generate Schema, then copy and paste the output into your schema.ts file
- Replace any jsonb() columns that represent relational data with a proper foreign key and a related table. Run pnpm drizzle-kit generate to create the migration
Why Use This Tool?
Tips & Best Practices
- Drizzle column names in the builder are the SQL column names, not the JavaScript property names. If you turn off snake_case, the SQL column will match your camelCase JSON key. That is valid but unconventional
- $inferSelect reflects every column, including those with .default(). $inferInsert makes columns with defaults optional and required columns required, which is how TypeScript catches missing INSERT arguments
- jsonb() stores arbitrary JSON and supports path queries in PostgreSQL (via the ->> and @> operators in raw SQL). If you query inner fields frequently, promote them to typed columns instead
- For UUID primary keys on PostgreSQL, use uuid().primaryKey().defaultRandom(). This tool detects UUID-shaped strings and will output the right column type
- After generating, add index() definitions manually on columns you filter or sort by. Drizzle does not infer indexes from data
Frequently Asked Questions
What is the complete JSON-to-Drizzle type mapping?
JSON integer maps to integer() or serial() for IDs. JSON float maps to numeric({ precision, scale }) on PG/MySQL, real() on SQLite. JSON boolean maps to boolean() on PG, tinyint({ mode: "boolean" }) on MySQL, integer({ mode: "boolean" }) on SQLite. JSON string maps to varchar({ length: 255 }). ISO date string maps to timestamp() / datetime() / text({ mode: "timestamp" }). UUID string maps to uuid() on PG, varchar({ length: 36 }) elsewhere. JSON object or array maps to jsonb() on PG, json() on MySQL/SQLite. JSON null means the field has no .notNull().
When should I use jsonb() versus a separate related table?
Use jsonb() for genuinely unstructured data you treat as an opaque blob: user preferences, third-party webhook payloads, audit snapshots. Use a separate table with a foreign key when the nested data has a stable shape, needs to be filtered or aggregated, or needs its own constraints. Arrays of objects in JSON (e.g. order line items) almost always belong in a separate related table, not a jsonb column.
What is the difference between $inferSelect and $inferInsert?
$inferSelect is the TypeScript type of a complete row as returned by SELECT, including every column and defaulted fields. $inferInsert is the type for INSERT, where columns with .default() or .defaultNow() become optional because Drizzle fills them in. This means TypeScript will error if you try to insert without providing a required column, but will not require you to supply createdAt or id on every insert.
How do I run migrations after generating the schema?
Install drizzle-kit with pnpm add -D drizzle-kit, add a drizzle.config.ts file pointing to your schema file and database URL, then run pnpm drizzle-kit generate to create a SQL migration file and pnpm drizzle-kit migrate (or push for prototyping) to apply it.
Is my data sent to a server?
No. All schema generation runs entirely in your browser. Your JSON never leaves your device.
Real-world Examples
User table from a signup API response
A POST /auth/signup response returns the created user. The id becomes serial().primaryKey(), email gets .unique(), the null bio becomes a nullable varchar, score is a numeric float, and audit timestamps are added automatically.
{
"id": 1,
"email": "ada@example.com",
"name": "Ada Lovelace",
"bio": null,
"score": 4.85,
"isVerified": true,
"createdAt": "2024-03-01T12:00:00Z"
}import { boolean, numeric, pgTable, serial, text, timestamp, varchar } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: varchar('email', { length: 255 }).notNull().unique(),
name: varchar('name', { length: 255 }).notNull(),
bio: text('bio'),
score: numeric('score', { precision: 10, scale: 2 }).notNull(),
isVerified: boolean('is_verified').notNull().default(true),
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull(),
});
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;Product catalog item: nested object becomes jsonb
A product has structured fields plus a specs object that is genuinely unstructured (different per category). The structured fields get typed columns; specs maps to jsonb(). This is the canonical pattern for semi-structured data.
{
"id": "prod_abc123",
"sku": "TS-RED-M",
"name": "Red T-Shirt Medium",
"price": 19.99,
"stock": 142,
"inStock": true,
"specs": { "color": "red", "size": "M", "weight_g": 180 },
"createdAt": "2024-06-01T09:00:00Z"
}import { boolean, integer, jsonb, numeric, pgTable, timestamp, varchar } from 'drizzle-orm/pg-core';
export const products = pgTable('products', {
id: varchar('id', { length: 255 }).notNull().primaryKey(),
sku: varchar('sku', { length: 255 }).notNull(),
name: varchar('name', { length: 255 }).notNull(),
price: numeric('price', { precision: 10, scale: 2 }).notNull(),
stock: integer('stock').notNull(),
inStock: boolean('in_stock').notNull().default(true),
specs: jsonb('specs').notNull(),
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull(),
});
export type Product = typeof products.$inferSelect;
export type NewProduct = typeof products.$inferInsert;