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 backup before 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 records
-
ViewRule
-
View individual records
-
CreateRule
-
Create records
-
UpdateRule
-
Update records
-
DeleteRule
-
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 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 (not Max ) for RelationField
-
Use MaxSize (not Max ) 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/