Docs
Database

Database

Drizzle ORM schema, migrations, and the 14-table structure.

Overview

The database layer uses Drizzle ORM with PostgreSQL. Any standard PostgreSQL provider works — the recommended options are:

  • Supabase — recommended; the boilerplate already uses Supabase for file storage, so you can consolidate both under one project
  • Neon — serverless PostgreSQL, scales to zero, pairs well with Vercel deployments
  • Self-hosted — any PostgreSQL 14+ instance via a standard DATABASE_URL connection string

The schema is split across three files, each responsible for a distinct domain:

Schema FileTables
lib/db/schema/account.tsusers, sessions, accounts, verifications, teams, teamMembers, invitations, activityLogs, monthlyUsages, dailyUsages
lib/db/schema/chat.tsconversations, messages
lib/db/schema/agent.tsmemories, workflows, workflowSteps, workflowRuns, assets

Key Design Decisions

messages.parts is JSONB
Stored as Array<UIMessagePart> — the Vercel AI SDK's UI message parts shape. This includes text parts, tool-call parts, tool-result parts, and tool-approval-request parts. Do not query inside this field — load the full row and parse in TypeScript.

assets.content is always text
Images are stored as URLs (strings). Structured outputs (posts, scripts, analyses) are stored as Markdown strings. This makes assets universally renderable without special cases.

teams has all Stripe fields
There is no separate subscriptions table. Stripe customer ID, subscription ID, price ID, plan name, and billing status all live directly on the teams row.

workflowRuns.conversationId FK
Every run gets its own conversation. This is the Chat-as-State pattern — the conversation history is the run's state. See Chat-as-State.

Drizzle Config

// drizzle.config.ts
export default defineConfig({
  schema: [
    "./lib/db/schema/account.ts",
    "./lib/db/schema/chat.ts",
    "./lib/db/schema/agent.ts",
  ],
  out: "./lib/db/migrations",
  dialect: "postgresql",
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});

Common Commands

npm run db:generate   # generate SQL migration files from your schema changes
npm run db:migrate    # apply pending migrations to the database

Run db:generate whenever you change a schema file. Commit the generated files alongside your code, then run db:migrate to apply them — locally and in production. See the Drizzle migrations docs for a deeper walkthrough.

In production, always run npm run db:migrate against a database backup first. Never use db:push in production — it applies schema changes without a migration history and can cause data loss on column type changes.

Adding a New Table

  1. Add the table definition to the appropriate schema file
  2. If creating a new schema file, add it to drizzle.config.ts
  3. Run npm run db:generate to create the migration file
  4. Run npm run db:migrate to apply it