Prisma Migrations
All schema changes go through PrismaPrismaThe TypeScript ORM HiveCFM uses to talk to Postgres. The schema lives at packages/database/schema.prisma. migrations. The workflow is: edit schema.prisma, then run a command that generates SQL and applies it. Never hand-edit tables in production with ALTER TABLE — the migration file is the durable record of the change.
Where things live
hivecfm-core/packages/database/
├── schema.prisma # Source of truth for the schema
├── migrations/ # One folder per migration, oldest first
│ ├── 20260131161101_add_whatsapp_sms_channels/
│ ├── 20260303000000_add_novu_integration_type/
│ ├── 20260304000000_campaign_novu_refactor/
│ ├── 20260307000000_add_tenant_license/
│ └── 20260405000000_add_under_review_status/
├── src/ # Seed scripts, generators
├── dist/ # Build output: generated client + migration runner
└── package.json # db:migrate:dev, db:migrate:deploy, db:seedEach migration folder contains a migration.sql file. The folder name is <timestamp>_<snake_case_description>; timestamps are generated when you run prisma migrate dev.
Adding a field — the normal workflow
Say you want to add nickname to the User model:
1. Edit schema.prisma
model User {
id String @id @default(cuid())
email String @unique
nickname String? // <- new field
// …
}2. Generate and apply the migration
cd hivecfm-core
pnpm run db:migrateUnder the hood this runs pnpm --filter @hivecfm/database db:migrate:dev, which:
- Builds
@hivecfm/database(vite build). - Runs
prisma generateto regenerate the typed client. - Runs the repo’s own
apply-migrations.js, which diffs the schema against the database, creates a new migration folder undermigrations/, and applies it.
The script will prompt for a migration name if one is not supplied via --name. Pick a short snake_case description — it becomes the folder suffix and shows up in every git log forever.
3. Commit the new migration folder
git add packages/database/schema.prisma packages/database/migrations/
git commit -m "feat(db): add User.nickname"The schema.prisma change and the generated migrations/<timestamp>_add_user_nickname/ folder both belong in the same commit. Code review verifies the SQL matches the schema diff.
Migration workflow in production
Production does not run db:migrate:dev. It runs the deploy variant:
pnpm --filter @hivecfm/database db:migrate:deployDifferences:
db:migrate:dev | db:migrate:deploy | |
|---|---|---|
| Generates new migrations | Yes (from schema diff) | No — applies only what exists in migrations/ |
| Regenerates the client | Yes | No (client ships with the built image) |
| Interactive | Asks for a migration name | Non-interactive |
| Uses | DATABASE_URL | MIGRATE_DATABASE_URL if set, else DATABASE_URL |
MIGRATE_DATABASE_URL lets ops run migrations through a privileged user while runtime uses a less-privileged one.
Resetting local dev — nuclear option
If your local DB is wedged and you do not care about the data:
cd hivecfm-core/packages/database
pnpm dlx prisma migrate resetThis drops the database, re-applies every migration, and re-runs the seed. Never run this against a shared or production database.
The friendlier alternative for a “I just want to iterate on the schema without making a migration yet” situation:
pnpm --filter @hivecfm/database db:pushdb:push does a direct ALTER TABLE that matches schema.prisma, skipping migration files entirely. Use it for spikes, never commit the change until you have generated a real migration.
Common pitfalls
- Forgetting to rebuild after a schema change.
db:migrate:devtakes care of it; if you edited the schema but used a different script, runpnpm --filter @hivecfm/database buildbefore calling the Prisma client. - Editing an applied migration. Do not. Add a new migration that amends the prior one instead.
- Shadow database errors.
prisma migrate devneeds a “shadow” database to diff against. In local dev the postgres container creates it automatically; if you hit “shadow database access denied”, the configured user is missingCREATE DATABASEgrant. - Stale client after
git pull. If TypeScript suddenly complains that a field does not exist, re-runpnpm --filter @hivecfm/database db:migrate:dev— it both migrates and regenerates the client.
.NET analogy
If you have used EF Core migrations, the flow is similar but inverted:
- EF: change C# model, run
dotnet ef migrations add, EF diffs model vs snapshot and writes SQL. - Prisma: change schema file, run
prisma migrate dev, Prisma diffs schema vs live DB and writes SQL.
There is no ModelSnapshot.cs — the migrations folder and the live schema are the full history.
Troubleshooting
See the Migration stuck runbook for the most common production failure modes.