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
go
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
go
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
go
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
go
// 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
go
// 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:
code
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 records - •
ViewRule- View individual records - •
CreateRule- Create records - •
UpdateRule- Update records - •
DeleteRule- Delete records
Common Patterns
go
// 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
go
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:
go
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 file - •
mise run migrate- Run pending migrations - •
mise run migratedown- Rollback last migration - •
mise run show-collections- Display collections - •
mise 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/