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_URLconnection string
The schema is split across three files, each responsible for a distinct domain:
| Schema File | Tables |
|---|---|
lib/db/schema/account.ts | users, sessions, accounts, verifications, teams, teamMembers, invitations, activityLogs, monthlyUsages, dailyUsages |
lib/db/schema/chat.ts | conversations, messages |
lib/db/schema/agent.ts | memories, 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 databaseRun 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
- Add the table definition to the appropriate schema file
- If creating a new schema file, add it to
drizzle.config.ts - Run
npm run db:generateto create the migration file - Run
npm run db:migrateto apply it