Skip to content

Add a Database Table

Edit shared/src/db/schema.ts. Follow conventions:

export const widgets = pgTable(
'widgets',
{
id: uuid('id').primaryKey().defaultRandom(),
user_id: uuid('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
name: text('name').notNull(),
status: text('status').notNull().default('active'),
created_at: timestamp('created_at', { mode: 'string', withTimezone: true })
.defaultNow()
.notNull(),
updated_at: timestamp('updated_at', { mode: 'string', withTimezone: true })
.defaultNow()
.notNull()
.$onUpdate(() => sql`now()`),
},
(table) => [
index('idx_widgets_user_id').on(table.user_id),
]
);
export type Widget = typeof widgets.$inferSelect;
export type NewWidget = typeof widgets.$inferInsert;

Conventions:

  • snake_case column names
  • uuid primary keys with defaultRandom()
  • mode: 'string' on all timestamps (returns ISO strings, not Date objects)
  • created_at + updated_at on every table
  • Foreign keys with explicit onDelete behavior
  • Indexes on columns used in WHERE clauses

Add the table and types to shared/src/index.ts:

export { widgets, type Widget, type NewWidget } from './db/schema';
Terminal window
bun run db:generate

Open the generated file in server/drizzle/. Check:

  • Table and column names are correct
  • Foreign key references point to the right table
  • No unexpected DROP statements
  • NOT NULL columns have DEFAULT values (or the table is new)
Terminal window
bun run db:migrate
Terminal window
bun run typecheck

For things Drizzle can’t generate — RLS policies, data backfills, enum types:

Terminal window
cd server && bunx drizzle-kit generate --custom

Write the SQL yourself in the generated empty file.