Comprehensive guide for PocketBase migrations (0.20+) using Go-based migration system...
Guide for creating PocketBase migrations using the Go-based system (0.20+).
ALWAYS follow this workflow:
mise run migratemise run show-collectionsmise run backup before destructive changesNever write multiple migrations without running them between each one.
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 := 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)
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)
See references/field-types.md for complete field type 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}
// 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,
})
Create collections in this order:
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
ListRule - List recordsViewRule - View individual recordsCreateRule - Create recordsUpdateRule - Update recordsDeleteRule - Delete records// 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")
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)
Always check errors:
collection, err := app.FindCollectionByNameOrId("posts")
if err != nil {
return err
}
if err := app.Save(collection); err != nil {
return err
}
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"github.com/pocketbase/pocketbase/tools/types"types.Pointer() for rule assignments and pointer valuesMaxSelect (not Max) for RelationFieldMaxSize (not Max) for EditorField"_pb_users_auth_"types.Pointer(0.0)types.Pointer("")