| name | pocketbase-migrations |
| description | Comprehensive guide for PocketBase migrations (0.20+) using Go-based migration system. Use when creating or modifying PocketBase collections, managing fields, setting up relations, or writing migration files. |
PocketBase Migrations
Guide for creating PocketBase migrations using the Go-based system (0.20+).
Core Workflow
Critical Migration Pattern
ALWAYS follow this workflow:
- Write ONE migration at a time
- Execute immediately with
mise run migrate - Verify it worked with
mise run show-collections - Only then write the next migration
- Run
mise run backupbefore destructive changes
Never write multiple migrations without running them between each one.
Migration Structure
package migrations
import (
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/types"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
// Up migration
return nil
}, func(app core.App) error {
// Down migration
return nil
})
}
Collection Operations
Create Base Collection
collection := core.NewBaseCollection("posts")
// Add fields
collection.Fields.Add(&core.TextField{
Name: "title",
Required: true,
Max: 100,
})
// Set rules (use types.Pointer())
collection.ListRule = types.Pointer("@request.auth.id != ''")
collection.CreateRule = types.Pointer("@request.auth.id != ''")
collection.UpdateRule = types.Pointer("@request.auth.id = author")
collection.DeleteRule = types.Pointer("@request.auth.id = author")
return app.Save(collection)
Update Existing Collection
collection, err := app.FindCollectionByNameOrId("posts")
if err != nil {
return err
}
// Add field
collection.Fields.Add(&core.DateField{
Name: "publishedAt",
})
// Remove field
collection.Fields.RemoveByName("oldField")
// Update rules
collection.UpdateRule = types.Pointer("@request.auth.id = author")
return app.Save(collection)
Common Field Types
See references/field-types.md for complete field type reference.
Quick Reference
// Text
&core.TextField{Name: "title", Required: true, Max: 100}
// Number
&core.NumberField{Name: "price", Min: types.Pointer(0.0)}
// Boolean
&core.BoolField{Name: "isActive"}
// Email
&core.EmailField{Name: "email", Required: true}
// Date
&core.DateField{Name: "publishedAt"}
// Auto-managed date
&core.AutodateField{Name: "created", OnCreate: true}
// Select
&core.SelectField{
Name: "status",
Values: []string{"draft", "published"},
MaxSelect: 1,
}
// File
&core.FileField{
Name: "avatar",
MaxSelect: 1,
MaxSize: 5242880, // bytes
}
// Editor (rich text)
&core.EditorField{
Name: "content",
MaxSize: 1048576,
}
// JSON
&core.JSONField{Name: "metadata", MaxSize: 65535}
Working with Relations
Best Practices
- Create collections in dependency order
- Fetch collections before creating relations
- Use actual collection IDs for relations
- Handle self-referencing relations in separate migrations
Relation Field
// Fetch the target collection first
authorsCollection, err := app.FindCollectionByNameOrId("authors")
if err != nil {
return err
}
// Add relation field
collection.Fields.Add(&core.RelationField{
Name: "author",
Required: true,
MaxSelect: 1, // Note: use MaxSelect, not Max
CollectionId: authorsCollection.Id,
CascadeDelete: true,
})
// System users collection
collection.Fields.Add(&core.RelationField{
Name: "creator",
CollectionId: "_pb_users_auth_",
MaxSelect: 1,
})
Migration Order for Relations
Create collections in this order:
- Independent collections (no relations)
- Collections depending on system collections
- Collections with relations to other custom collections
- Self-referencing relations (separate migration)
Example order:
1_create_categories.go # Independent
2_create_authors.go # Depends on system users
3_create_posts.go # Depends on authors & categories
4_create_comments.go # Depends on posts & users
5_add_parent_to_comments.go # Self-referencing
Collection Rules
Rule Types
ListRule- List recordsViewRule- View individual recordsCreateRule- Create recordsUpdateRule- Update recordsDeleteRule- Delete records
Common Patterns
// Public access
types.Pointer("")
// Authenticated only
types.Pointer("@request.auth.id != ''")
// Owner only
types.Pointer("@request.auth.id = author")
// Published or owner
types.Pointer("status = 'published' || author = @request.auth.id")
// Through relations
types.Pointer("author.user = @request.auth.id")
View Collections
viewQuery := `
SELECT
posts.id,
posts.title,
users.name as author_name
FROM posts
JOIN users ON posts.author = users.id
`
collection := core.NewViewCollection("posts_with_authors", viewQuery)
return app.Save(collection)
Error Handling
Always check errors:
collection, err := app.FindCollectionByNameOrId("posts")
if err != nil {
return err
}
if err := app.Save(collection); err != nil {
return err
}
Task Commands
mise run makemigration <name>- Create new migration filemise run migrate- Run pending migrationsmise run migratedown- Rollback last migrationmise run show-collections- Display collectionsmise run backup- Backup database to /tmp
Best Practices
- Import types package for nullable values:
"github.com/pocketbase/pocketbase/tools/types" - Use
types.Pointer()for rule assignments and pointer values - Check collection existence before creating relations
- Validate field names don't conflict with system fields (id, created, updated)
- Handle errors immediately after operations
- Never skip migration testing - run each one before writing the next
- Backup before destructive changes
Common Gotchas
- Use
MaxSelect(notMax) for RelationField - Use
MaxSize(notMax) for EditorField - System users collection ID:
"_pb_users_auth_" - For number min/max:
types.Pointer(0.0) - Empty rules need:
types.Pointer("") - Self-referencing relations need separate migrations
Additional Resources
- Field types reference: references/field-types.md
- Official docs: https://pocketbase.io/docs/go-collections/