migrations - Innovix-Matrix-Systems/ims-pocketbase-baas-starter GitHub Wiki
Database Migrations Guide
This document outlines the database migration strategy and best practices for the IMS PocketBase BaaS Starter project.
Overview
Our migration system uses PocketBase's built-in migration framework with an incremental approach for adding new collections and schema changes. This ensures safe, reversible database changes in both development and production environments.
Migration Strategy
Initial Migration (0001_init.go)
The initial migration imports the complete database schema from 0001_pb_schema.json and sets up:
- All base collections (users, roles, permissions)
- System collections (_superusers, _authOrigins, etc.)
- Application settings from environment variables
- Initial data seeding (superuser, RBAC data)
Future Migrations (Incremental Approach)
For new collections or schema changes, we use targeted migrations that only affect specific collections:
- Export only new collections from PocketBase Admin UI
- Create numbered migration files with specific changes
- Import only the new collections in the migration
- Provide precise rollback functionality
File Structure
internal/database/
├── migrations/
│ ├── 0001_init.go # Initial schema and setup
│ ├── 0002_add_user_settings.go # User settings collections
│ ├── 0003_add_audit_logs.go # Example: Audit logging
│ └── utils.go # Migration helper functions
├── schema/
│ ├── 0001_pb_schema.json # Complete initial schema
│ ├── 0002_pb_schema.json # User settings collections for migration 0002
│ ├── 0003_pb_schema.json # Example: Future schema additions
│ └── README.md # Schema documentation
└── seeders/
├── rbac_seeder.go # Role-based access control seeding
└── superuser_seeder.go # Superuser creation
Migration CLI Generator
The project includes a CLI tool to automatically generate migration files with proper structure and naming conventions.
Quick Start
Generate a new migration using the makefile:
make migrate-gen name=add_user_profiles
This will:
- Scan existing migrations to determine the next sequential number
- Create a properly structured migration file (e.g.,
0003_add_user_profiles.go) - Display the expected schema file location
- Provide next steps for completing the migration
Usage Examples
# Generate migration with underscore naming
make migrate-gen name=add_user_settings
# Generate migration with hyphen naming
make migrate-gen name=add-audit-logs
# Generate migration with mixed case (will be converted to kebab-case)
make migrate-gen name=AddNotificationSystem
Generated File Structure
The CLI generates migration files with this structure:
package migrations
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
// Forward migration
schemaPath := filepath.Join("internal", "database", "schema", "0003_pb_schema.json")
// ... schema import logic
// TODO: Add any data seeding specific to these collections
return nil
}, func(app core.App) error {
// Rollback migration
collectionsToDelete := []string{
// TODO: Add collection names to delete during rollback
}
// ... rollback logic
return nil
})
}
CLI Features
- Automatic Numbering: Scans existing migrations and assigns the next sequential number
- Name Sanitization: Converts migration names to kebab-case format
- Input Validation: Ensures migration names contain only valid characters
- Duplicate Prevention: Prevents overwriting existing migration files
- Helpful Output: Shows file paths and next steps after generation
Building the CLI
To build a standalone binary:
make migrate-gen-build
This creates bin/migrate-gen which can be used directly:
./bin/migrate-gen add_user_profiles
Creating New Migrations
Step 1: Generate Migration File
Use the CLI generator to create the migration file:
make migrate-gen name=your_migration_name
Step 2: Design Collections
- Use PocketBase Admin UI to design your new collections
- Test the collections thoroughly in development
- Document the purpose and relationships
Step 3: Export Schema
- Export only the new collections from PocketBase Admin UI
- Save as
internal/database/schema/XXXX_pb_schema.json(the CLI will tell you the exact filename) - The schema file number should match your migration number
Step 4: Update Migration File
The generated migration file includes TODO comments for customization:
// internal/database/migrations/0002_add_user_profiles.go
package migrations
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
// Forward migration
schemaPath := filepath.Join("internal", "database", "schema", "0002_pb_schema.json")
schemaData, err := os.ReadFile(schemaPath)
if err != nil {
return fmt.Errorf("failed to read schema file: %w", err)
}
var collections []interface{}
if err := json.Unmarshal(schemaData, &collections); err != nil {
return fmt.Errorf("failed to parse schema JSON: %w", err)
}
collectionsData, err := json.Marshal(collections)
if err != nil {
return fmt.Errorf("failed to marshal collections: %w", err)
}
if err := app.ImportCollectionsByMarshaledJSON(collectionsData, false); err != nil {
return fmt.Errorf("failed to import collections: %w", err)
}
// Optional: Add any data seeding specific to these collections
return nil
}, func(app core.App) error {
// Rollback migration
collectionsToDelete := []string{"settings", "user_settings"}
for _, collectionName := range collectionsToDelete {
collection, err := app.FindCollectionByNameOrId(collectionName)
if err != nil {
continue // Collection might not exist
}
if err := app.Delete(collection); err != nil {
return fmt.Errorf("failed to delete collection %s: %w", collectionName, err)
}
}
return nil
})
}
Step 5: Test Migration
# Test the migration
make dev
# Check logs to ensure migration runs successfully
make dev-logs
# Test rollback if needed (in development only)
Migration Best Practices
Naming Conventions
- Migration files:
XXXX_descriptive_name.go(e.g.,0002_add_user_settings.go) - Schema files:
XXXX_pb_schema.json(e.g.,0002_pb_schema.json) - Collection names: Use snake_case (e.g.,
settings,user_settings)
Safety Guidelines
- Always test migrations in development first
- Provide rollback functionality for every migration
- Keep migrations focused - one feature per migration
- Document breaking changes in migration comments
- Backup production data before running migrations
Data Seeding in Migrations
- Initial data only: Use seeders for essential data (roles, permissions)
- Make seeding idempotent: Check if data exists before creating
- Separate concerns: Keep schema changes and data seeding separate when possible
Environment Considerations
- Development: Migrations run automatically with
automigrate: true - Production: Run migrations manually with proper backup procedures
- Testing: Use separate test databases for migration testing
Common Migration Patterns
Adding a New Collection
// Import new collection from JSON schema file
// Add any required initial data
// Provide rollback to delete the collection
Modifying Existing Collection
// Export the modified collection
// Import with updated schema
// Handle data migration if field types changed
// Provide rollback to previous schema
Adding Relationships
// Ensure related collections exist
// Add relation fields
// Update access rules if needed
// Test cascade delete behavior
Troubleshooting
Common Issues
- Schema conflicts: Ensure collection IDs don't conflict
- Missing dependencies: Check if related collections exist
- Permission errors: Verify access rules are correctly set
- Data type conflicts: Handle field type changes carefully
Recovery Procedures
- Failed migration: Use rollback function to revert changes
- Corrupted data: Restore from backup and retry
- Schema inconsistency: Export current schema and compare with expected
Migration Checklist
Before creating a migration:
- Collections designed and tested in development
- Schema exported to numbered JSON file
- Migration file created with proper rollback
- Migration tested locally
- Documentation updated
- Backup procedures planned for production
Common Migration Examples
Adding User Profile Collections
# Generate the migration
make migrate-gen name=add_user_profiles
# Design collections in PocketBase Admin UI:
# - user_profiles (relation to users)
# - profile_settings
# - user_preferences
# Export to internal/database/schema/0003_pb_schema.json
# Update rollback function:
collectionsToDelete := []string{"user_profiles", "profile_settings", "user_preferences"}
Implementing Audit Logging
# Generate the migration
make migrate-gen name=add_audit_logs
# Design collections:
# - audit_logs (user actions, timestamps, metadata)
# - audit_settings (retention policies)
# Export to internal/database/schema/0004_pb_schema.json
# Update rollback function:
collectionsToDelete := []string{"audit_logs", "audit_settings"}
Setting Up Notification System
# Generate the migration
make migrate-gen name=add_notification_system
# Design collections:
# - notifications (user notifications)
# - notification_templates (email/push templates)
# - notification_preferences (user preferences)
# Export to internal/database/schema/0005_pb_schema.json
# Update rollback function:
collectionsToDelete := []string{"notifications", "notification_templates", "notification_preferences"}
Content Management Collections
# Generate the migration
make migrate-gen name=add_cms_collections
# Design collections:
# - articles (blog posts, content)
# - categories (content categorization)
# - tags (content tagging)
# - media (file attachments)
# Export to internal/database/schema/0006_pb_schema.json
# Update rollback function:
collectionsToDelete := []string{"articles", "categories", "tags", "media"}
CLI Usage Patterns
# Different naming styles (all converted to kebab-case)
make migrate-gen name=add_user_settings # → 0003_add-user-settings.go
make migrate-gen name=AddUserSettings # → 0003_adduserasettings.go
make migrate-gen name=add-user-settings # → 0003_add-user-settings.go
# Complex migration names
make migrate-gen name=refactor_user_authentication_system
# → 0004_refactor-user-authentication-system.go
# Simple migrations
make migrate-gen name=fix_permissions # → 0005_fix-permissions.go
make migrate-gen name=update_schema # → 0006_update-schema.go
Support
For migration-related issues:
- Check the troubleshooting section above
- Review PocketBase migration documentation
- Test in development environment first
- Create an issue with detailed error logs