IMPORTANT: All components should follow the "CMS-first with defaults" pattern:
- Always fetch from CMS first - Every component should attempt to get content from Payload CMS
- Provide comprehensive defaults - When CMS data is unavailable, use branded fallback content
- Never hardcode content in components - All text, images, and data should be configurable
// Good - CMS with defaults
const content = cmsData?.title || "Default Brand Title"
// Bad - Hardcoded only
const content = "Fixed Title"- Fetch data using
getGlobalSettings()orgetPayloadData() - Wrap in try/catch with console.error logging
- Return default content on error
- Use proper TypeScript types from generated payload-types
- Include loading states with Suspense boundaries
- Development (Push Mode): Automatically syncs schema changes to database - NO migration files needed
- Production (Migration Mode): REQUIRES migration files for all schema changes - automatic sync is disabled
Create a migration file BEFORE deploying to production whenever you:
- Add/remove/modify fields in collections or globals
- Add/remove collections or globals
- Change field types, validations, or relationships
- Modify access control or hooks that affect schema
| Command | Purpose | When to Use |
|---|---|---|
pnpm migrate:create |
Creates a new migration file | After making schema changes, before deploying |
pnpm migrate:create [name] |
Creates named migration | For specific features: pnpm migrate:create add_pricing_fields |
pnpm migrate |
Runs pending migrations | In production/staging deployments |
echo "y" | pnpm migrate |
Auto-confirm migration prompts | When prompted about dev mode changes |
pnpm migrate:status |
Shows migration status | To check which migrations have run |
pnpm migrate:down |
Rolls back last batch | If a migration fails (use cautiously) |
echo "y" | pnpm migrate:fresh |
Drops all data and reruns | NEVER in production - dev only |
Important Notes:
- When running
pnpm migrate, you'll often see: "It looks like you've run Payload in dev mode..." - This warning appears because dev mode auto-syncs schema changes
- Answer "y" to proceed - the migration will still track properly
- Use
echo "y" | pnpm migrateto auto-confirm in scripts
# Start dev server - schema changes auto-sync
pnpm dev
# Make your Payload config changes
# Test thoroughly# Stop dev server first!
# Create migration for your changes
pnpm migrate:create
# If making specific feature changes, use descriptive name:
pnpm migrate:create add_customer_testimonials
# Review the generated migration file in src/migrations/
# Commit both your config changes AND migration files# This runs automatically in our build process
pnpm migrate
pnpm build- Location:
src/migrations/(MUST be in version control) - Naming:
[timestamp]_[optional_description].ts - Format: TypeScript files with up/down functions
- Generated by: Payload analyzes schema differences
Our package.json includes these scripts:
"migrate": "payload migrate",
"migrate:create": "payload migrate:create",
"migrate:status": "payload migrate:status",
"build": "pnpm migrate && next build",
"build:safe": "pnpm migrate:status && pnpm migrate && next build"Note: If migrations fail in CI/CD due to prompts, update the build script:
"build": "echo 'y' | pnpm migrate && next build"- Vercel runs
pnpm build - Our build script runs
pnpm migratefirst - Migrations execute against production database
- If migrations fail, build stops (deployment fails)
- If successful, Next.js build proceeds
| Issue | Cause | Solution |
|---|---|---|
| "column does not exist" in production | Missing migration file | Create migration locally, deploy again |
| "no migrations to run" | Already up to date | Normal - build continues |
| Migration create shows no changes | Dev mode already synced | Normal - create empty migration with --force-accept-warning |
| "Cannot run migrations in push mode" | Trying to migrate in dev | Stop dev server before creating migrations |
| Build hangs on migration prompt | Waiting for y/n input | Use echo "y" | pnpm migrate in build scripts |
| "data loss will occur" warning | Dev mode pushed changes | Normal - answer "y" to proceed |
-
ALWAYS create migrations before deploying
# After any Payload config change: pnpm migrate:create git add src/migrations/ git commit -m "Add migration for [feature]"
-
Test migrations in staging first
- Deploy to preview/staging environment
- Verify migrations run successfully
- Then deploy to production
-
Coordinate schema changes in team
- Communicate when creating migrations
- Avoid concurrent schema modifications
- Pull latest migrations before creating new ones
-
Never modify migration files
- Once created and committed, don't edit
- Create new migrations for fixes
- Maintains migration history integrity
-
Include migrations in Docker builds
COPY src/migrations /app/src/migrations
- Migrations are MANDATORY for PostgreSQL in production
- Development push mode works with local Postgres
- Production Postgres requires explicit migrations
- Transactions ensure all-or-nothing migration execution
Always run these commands before committing:
pnpm typecheck # Verify TypeScript types
pnpm lint # Check code quality
pnpm migrate:status # Check migration statusWhen adding new fields to Payload collections or globals:
- Update Payload Config - Add fields to collections/globals
- Generate Types - Run
pnpm generate:types - Create Migration - Run
pnpm migrate:create [description] - Update Components - Use the newly generated types
- Test & Verify - Run typecheck and lint
- Commit Everything - Config, types, migrations, and component changes
Important: Always update Payload config FIRST before updating components to ensure type safety with auto-generated types.
- Payload config changes made?
- Dev server stopped?
- Types generated with
pnpm generate:types? - Migration created with
pnpm migrate:create? - Migration file reviewed in
src/migrations/? - Migration file committed to git?
- Components updated with new types?
- TypeScript/lint checks pass?
- Tested in local environment?
Ensure all required environment variables are set in production:
DATABASE_URI- PostgreSQL connection string (Neon)PAYLOAD_SECRET- Used for JWT encryptionNEXT_PUBLIC_SERVER_URL- Public URL of the applicationPOSTGRES_PRISMA_URL- Pooled connection for PrismaPOSTGRES_URL_NON_POOLING- Direct connection for migrations