Wasp Database Migration Skill
Quick Reference
When to use this skill:
- •Modifying database schema
- •Adding/changing models or fields
- •Running migrations
- •Database setup and configuration
- •Schema errors or type mismatches
Key concepts:
- •ALWAYS use
wasp db migrate-dev(NEVERprisma migrate dev) - •MANDATORY restart after migration for types to update
- •PostgreSQL required for enums and PgBoss jobs
- •Migration files are immutable (never edit/delete)
Critical Rules
NEVER use: npx prisma migrate dev (WRONG)
ALWAYS use: wasp db migrate-dev (CORRECT)
MANDATORY: Restart ../scripts/safe-start.sh after migration (multi-worktree safe, types regenerate)
NO exceptions: Even "simple" changes require restart - types will be stale without it
Complete Migration Workflow
Step 1: Edit schema.prisma
Location: app/schema.prisma
Common schema patterns:
// Primary key with auto-increment
model Task {
id Int @id @default(autoincrement())
description String
isDone Boolean @default(false)
createdAt DateTime @default(now())
// Relations
user User @relation(fields: [userId], references: [id])
userId Int
}
// Optional fields (add ?)
model User {
id Int @id @default(autoincrement())
email String? @unique // Optional
username String @unique // Required
}
// Enums (requires PostgreSQL)
enum TaskStatus {
TODO
IN_PROGRESS
DONE
}
model Task {
status TaskStatus @default(TODO)
}
// Unique constraints
model User {
email String @unique
@@unique([organizationId, username]) // Composite unique
}
Common field types:
- •
String- Text - •
Int- Integer - •
Boolean- True/false - •
DateTime- Timestamp - •
Json- JSON data - •
String?- Optional string - •Add
@uniquefor unique constraint - •Add
@default(...)for default value
Step 2: Run Migration
wasp db migrate-dev --name "Descriptive migration name"
Examples of good migration names:
- •✅ "Add email to User"
- •✅ "Create Task model"
- •✅ "Add status field to Task"
- •✅ "Add unique constraint to username"
- •❌ "Update" (too vague)
- •❌ "Changes" (not descriptive)
- •❌ "Migration" (meaningless)
What this command does:
- •Generates SQL migration file in
app/migrations/ - •Applies migration to database
- •Updates Prisma client
- •DOES NOT regenerate Wasp types (requires restart!)
Command output:
Applying migration `20251018120000_add_email_to_user` ✓ Generated Prisma Client to ./node_modules/@prisma/client
Step 3: RESTART Wasp (MANDATORY)
# Stop current wasp (Ctrl+C), then safe-start (multi-worktree safe) ../scripts/safe-start.sh
Why restart is MANDATORY:
- •Wasp types only regenerate on restart
- •TypeScript won't see new fields without restart
- •NO exceptions - ALWAYS restart after migration
- •Even for "small" changes like adding one field
Common error if you skip restart:
Property 'email' does not exist on type 'User'
Fix: Stop wasp (Ctrl+C) and run ../scripts/safe-start.sh (multi-worktree safe)
Step 4: Verify Types Updated
Check that new fields appear in TypeScript imports:
import type { Task } from "wasp/entities";
// Task type now includes new fields
const task: Task = {
id: 1,
description: "Test",
status: "TODO", // New field visible after restart
createdAt: new Date(),
};
If fields still missing:
- •Confirm migration ran successfully
- •Verify you restarted wasp
- •Check for TypeScript cache issues:
wasp clean && ../scripts/safe-start.sh
Database Configuration
schema.prisma Structure
Required structure:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql" // or "sqlite" for development
url = env("DATABASE_URL") // ✅ MUST use env()
}
// Your models below
model User {
id Int @id @default(autoincrement())
}
Critical rules:
- •✅ Use
env("DATABASE_URL")for database URL - •❌ NEVER hardcode database URLs
- •✅ Keep schema.prisma in
app/schema.prisma - •❌ DO NOT configure database in main.wasp
PostgreSQL vs SQLite
PostgreSQL (Production & Recommended):
- •✅ Supports Prisma enums
- •✅ Supports PgBoss (background jobs)
- •✅ Production-ready
- •✅ Better performance
- •❌ Requires PostgreSQL server
SQLite (Development Only):
- •✅ No setup required
- •✅ Quick for prototyping
- •❌ NO enum support
- •❌ NO PgBoss support
- •❌ Limited concurrent writes
Recommendation: Use PostgreSQL from the start if you need enums or jobs.
Switch from SQLite to PostgreSQL:
// Before (SQLite)
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
// After (PostgreSQL)
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
Then update .env.server:
DATABASE_URL="postgresql://username:password@localhost:5432/myapp_dev"
Common Migration Errors
Error: "Cannot find module 'wasp/entities'"
Cause: Forgot to restart wasp after migration
Fix:
# Stop wasp (Ctrl+C), then safe-start (multi-worktree safe) ../scripts/safe-start.sh
Error: "Property 'newField' does not exist on type 'User'"
Cause: Types not regenerated (forgot restart)
Fix: Restart ../scripts/safe-start.sh (multi-worktree safe)
Verification:
import type { User } from "wasp/entities";
// Check if type includes new field in IDE autocomplete
Error: "Migration failed: relation already exists"
Cause: Database out of sync with migrations
Fix (Development only!):
# WARNING: This deletes all data! wasp db reset wasp db migrate-dev "Fresh start" ../scripts/safe-start.sh # Multi-worktree safe
Production fix: Never use db reset in production. Contact database admin.
Error: "Enum types are not supported by sqlite"
Cause: Using enums with SQLite
Fix: Switch to PostgreSQL:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
Update .env.server:
DATABASE_URL="postgresql://localhost:5432/myapp_dev"
Error: "Environment variable not found: DATABASE_URL"
Cause: Missing .env.server file
Fix: Create app/.env.server:
DATABASE_URL="postgresql://localhost:5432/myapp_dev"
Best Practices
✅ DO:
- •Use descriptive migration names
- •Commit migration files to git
- •Restart immediately after migration
- •Use
wasp db migrate-devcommand - •Use
env("DATABASE_URL")in schema.prisma - •Test migrations in development first
- •Keep schema.prisma in app root
❌ NEVER:
- •Delete migration files (breaks history)
- •Edit existing migrations (create new one instead)
- •Use
prisma migrate devdirectly - •Skip restart after migration
- •Hardcode database URLs
- •Force migrations in production
- •Use
wasp db resetin production
Migration Workflow Examples
Example 1: Add Field to Existing Model
Scenario: Add email field to User model
Step 1: Edit schema.prisma
// Before
model User {
id Int @id @default(autoincrement())
}
// After
model User {
id Int @id @default(autoincrement())
email String? @unique
}
Step 2: Run migration
wasp db migrate-dev "Add email to User"
Step 3: Restart wasp
# Ctrl+C to stop, then safe-start (multi-worktree safe) ../scripts/safe-start.sh
Step 4: Verify in code
import type { User } from "wasp/entities";
// email field now available on User type
Example 2: Create New Model
Scenario: Create Task model with relations
Step 1: Edit schema.prisma
model Task {
id Int @id @default(autoincrement())
description String
isDone Boolean @default(false)
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
userId Int
}
model User {
id Int @id @default(autoincrement())
tasks Task[] // Add back-relation
}
Step 2: Run migration
wasp db migrate-dev "Create Task model"
Step 3: Restart wasp
Example 3: Add Relation Between Models
Scenario: Add User-Task relation
Step 1: Edit schema.prisma
model User {
id Int @id @default(autoincrement())
tasks Task[] // Add this line
}
model Task {
id Int @id @default(autoincrement())
description String
user User @relation(fields: [userId], references: [id])
userId Int
}
Step 2: Run migration
wasp db migrate-dev "Add User-Task relation"
Step 3: Restart wasp
Example 4: Add Enum (PostgreSQL only)
Scenario: Add TaskStatus enum
Step 1: Verify PostgreSQL
datasource db {
provider = "postgresql" // Required for enums
}
Step 2: Add enum to schema.prisma
enum TaskStatus {
TODO
IN_PROGRESS
DONE
}
model Task {
id Int @id @default(autoincrement())
status TaskStatus @default(TODO)
}
Step 3: Run migration
wasp db migrate-dev "Add TaskStatus enum"
Step 4: Restart wasp
Step 5: Use in code
import type { TaskStatus } from "wasp/entities"; // Type import
import { TaskStatus } from "@prisma/client"; // Value import
// Usage
const status: TaskStatus = TaskStatus.TODO;
PostgreSQL Setup
Local Development (macOS)
# Install PostgreSQL via Homebrew brew install postgresql brew services start postgresql # Create database createdb myapp_dev
Local Development (Linux)
# Install PostgreSQL sudo apt-get update sudo apt-get install postgresql postgresql-contrib # Start service sudo service postgresql start # Create database sudo -u postgres createdb myapp_dev
.env.server Configuration
Create/update: app/.env.server
DATABASE_URL="postgresql://username:password@localhost:5432/myapp_dev"
Format breakdown:
- •
postgresql://- Protocol - •
username:password- Database credentials - •
localhost:5432- Host and port - •
myapp_dev- Database name
Example for local development:
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/myapp_dev"
Docker Alternative
Start PostgreSQL in Docker:
docker run --name postgres \ -e POSTGRES_PASSWORD=postgres \ -p 5432:5432 \ -d postgres
Create database:
docker exec -it postgres psql -U postgres -c "CREATE DATABASE myapp_dev;"
.env.server:
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/myapp_dev"
Troubleshooting
Database connection failed
Check 1: PostgreSQL running
pg_isready # Expected: localhost:5432 - accepting connections
Check 2: DATABASE_URL in .env.server
cat app/.env.server # Verify DATABASE_URL is set
Check 3: Database exists
psql $DATABASE_URL -c "SELECT 1;" # Should connect successfully
Migrations out of sync
Development only - reset database:
# WARNING: Deletes all data! wasp db reset wasp db migrate-dev "Fresh start" ../scripts/safe-start.sh # Multi-worktree safe
Production - manual sync:
- •Never use
db reset - •Contact database admin
- •Apply migrations manually if needed
Types not showing new fields
Always the same fix: Restart ../scripts/safe-start.sh (multi-worktree safe)
If still broken:
wasp clean ../scripts/safe-start.sh # Multi-worktree safe
Enum not working
Check 1: PostgreSQL?
datasource db {
provider = "postgresql" // NOT sqlite
}
Check 2: Import values correctly?
// ✅ CORRECT - Runtime values
import { TaskStatus } from "@prisma/client";
const status = TaskStatus.TODO;
// ✅ CORRECT - Type annotation
import type { TaskStatus } from "wasp/entities";
const status: TaskStatus = "TODO";
// ❌ WRONG - Runtime values from wasp/entities
import { TaskStatus } from "wasp/entities"; // Type only, not values!
Quick Checklist
Migration workflow:
- • Edit schema.prisma
- • Run
wasp db migrate-dev "Description" - • Wait for completion
- • RESTART
../scripts/safe-start.sh(Ctrl+C, then safe-start - multi-worktree safe) - • Verify types updated in TypeScript
Database setup:
- • PostgreSQL installed and running (for enums/jobs)
- • DATABASE_URL set in .env.server
- • Database created
- • Connection tested
After changes:
- • Migration file committed to git
- • Types visible in IDE autocomplete
- • No TypeScript errors
- • Application runs without errors
Critical Commands
# Run migration (CORRECT) wasp db migrate-dev "Description" # Reset database (dev only - DELETES DATA!) wasp db reset # Open Prisma Studio (database GUI) wasp db studio # Check migration status wasp db migrate status
References
Complete guide:
- •
docs/TROUBLESHOOTING-GUIDE.md(Database Issues section) - •
.tmp/extraction/wave1-agent-a-cursor-rules.md(Migration workflow)
Related skills:
- •
wasp-operations- For using entities in operations - •
tdd-workflow- For testing database changes
External docs:
- •Prisma Schema: https://www.prisma.io/docs/concepts/components/prisma-schema
- •Wasp Database: https://wasp.sh/docs/data-model/entities