Convert PostgreSQL schemas to Drizzle
Review real Drizzle pgTable and relations output generated from Trellis’s 13-table seed schema, with unsupported constructs kept visible.
- target
- Drizzle schema
- access
- Pro export
- source specimen
- 2 of 13 tables · seed/schema.json
Trellis exports a schema drafted on its canvas using PostgreSQL types. This page does not claim to parse a PostgreSQL dump or a live database.
01 / assembly
A schema module, assembled in Drizzle’s own layers
Imports, pgTable declarations and relations blocks are all produced by the exporter. The excerpt preserves each selected block in full.
- 01 · imports
- Builders come from drizzle-orm and pg-core.
- 02 · table
- Columns, defaults, references, keys and indexes are declared together.
- 03 · relations
- Relation helpers follow the table declarations.
import { relations, sql } from 'drizzle-orm'
import { AnyPgColumn, char, customType, date, index, inet, integer, jsonb, numeric, pgTable, primaryKey, text, timestamp, uniqueIndex, uuid, vector } from 'drizzle-orm/pg-core'
// Drizzle schema generated from the schema canvas
// One row is one person who can sign in. An account is never deleted once it has authored anything; closing it sets the status instead, so authorship survives.
export const account = pgTable('account', {
id: uuid('id').notNull().defaultRandom(),
email: customType<{ data: string }>({ dataType() { return 'citext' } })('email').notNull(),
display_name: text('display_name').notNull(),
status: text('status').notNull().default("active"),
password_hash: text('password_hash'),
created_at: timestamp('created_at', { withTimezone: true, mode: 'string' }).notNull().default(sql.raw('now()')),
updated_at: timestamp('updated_at', { withTimezone: true, mode: 'string' }).notNull().default(sql.raw('now()')),
}, (table) => ({
// not expressible in Drizzle: primary key (id)
account_email: uniqueIndex('account_email_uniq').on(table.email),
account_status: index('account_status_idx').on(table.status),
}))
export const session = pgTable('session', {
id: uuid('id').notNull().defaultRandom(),
account_id: uuid('account_id').notNull().references(() => account.id),
token: text('token').notNull(),
ip: inet('ip'),
user_agent: text('user_agent'),
expires_at: timestamp('expires_at', { withTimezone: true, mode: 'string' }).notNull(),
created_at: timestamp('created_at', { withTimezone: true, mode: 'string' }).notNull().default(sql.raw('now()')),
}, (table) => ({
// not expressible in Drizzle: primary key (id)
session_token: uniqueIndex('session_token_uniq').on(table.token),
session_account_id: index('session_account_id_idx').on(table.account_id),
session_expires_at: index('session_expires_at_idx').on(table.expires_at),
}))
export const sessionRelations = relations(session, ({ one }) => ({
account: one(account, { fields: [session.account_id], references: [account.id] }),
}))02 / raw SQL boundary
Unsupported index grammar stays visible
The generator writes a comment at the exact table extra it could not translate without raw SQL.
The full 13-table export writes its caveats into the generated file. These examples are copied from it:
// not expressible in Drizzle: check (entity_type in ('account', 'article', 'article_revision', 'comment', 'subscription', 'invoice', 'tag'))// not expressible in Drizzle without raw SQL: index (occurred_at desc)// not expressible in Drizzle without raw SQL: index (actor_id, occurred_at desc)// not expressible in Drizzle without raw SQL: index using gin (title gin_trgm_ops)
source / generated Drizzle schema output