Database
Drizzle owns the schema (src/db/schema.ts) and generates SQL; the Supabase CLI applies it. There is
one migration history, in supabase/migrations/ (Supabase filename format).
Changing the schema
Section titled “Changing the schema”# 1. edit src/db/schema.tspnpm db:generate --name=add_something # writes supabase/migrations/<ts>_add_something.sql# 2. review the generated SQLpnpm db:migrate # apply locally (or pnpm db:reset to start fresh)- Never hand-edit generated migrations. Don’t use
drizzle-kit push/drizzle-kit migrateor schema edits in Studio — they bypass Drizzle’s journal. - For raw SQL (functions, triggers, grants):
pnpm db:generate --custom --name=<name>, then fill in the empty file. pnpm db:checkchecks the migration journal;pnpm db:typesregenerates the Supabase types.- Applying migrations to a hosted project: see Self-hosting.
Conventions: bigint identity primary keys, timestamptz, indexed foreign keys, money as integer
cents, enums via pgEnum, casing: "snake_case" (camelCase in TypeScript, snake_case in SQL).
| File | Contains | Runs |
|---|---|---|
supabase/seed.sql |
Shared seed data (empty for now; never accounts) | db:start / db:reset |
supabase/dev/seed-users.sql |
One user per role — see Getting started | pnpm db:seed:dev (local only) |
supabase/dev/seed-users.sql is deliberately not in [db.seed] sql_paths: files there can be
pushed to production with supabase db push --include-seed.
Access
Section titled “Access”- Drizzle (
src/db/client.server.ts) connects aspostgresand bypasses row-level security. Use it only from*.server.tscode called by server functions, and authorize there. - Supabase clients (
src/lib/supabase/):getSupabaseServerClient()— per request, acts as the signed-in user (cookies).getSupabaseAdminClient()— secret key, bypasses RLS, server only (user management).getSupabaseBrowserClient()— browser singleton.
- App tables have RLS enabled with no policies and no
anon/authenticatedgrants, so the Data API can’t read them. Add policies and grants deliberately if a table should be exposed. - The pool (
pg, max 5, 5 s idle timeout) is registered withattachDatabasePoolfrom@vercel/functions, which closes idle connections before a Vercel instance suspends.