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.
actual exporter output2 of 13 tables · account, session
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