4.2 KiB
4.2 KiB
title, impact, impactDescription, tags
| title | impact | impactDescription | tags |
|---|---|---|---|
| Version Your Schema with Go Migrations | HIGH | Guarantees repeatable, transactional schema evolution and eliminates manual dashboard changes in production | go, migrations, schema, database, migratecmd, extending |
Version Your Schema with Go Migrations
PocketBase ships with a migratecmd plugin that generates versioned .go migration files, applies them automatically on serve, and lets you roll back with migrate down. Because the files are compiled into your binary, no extra migration tool is needed.
Incorrect (one-off SQL or dashboard changes in production):
// ❌ Running raw SQL directly at startup without a migration file –
// the change is applied every restart and has no rollback path.
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
_, err := app.DB().NewQuery(
"ALTER TABLE posts ADD COLUMN summary TEXT DEFAULT ''",
).Execute()
return err
})
// ❌ Forgetting to import the migrations package means
// registered migrations are never executed.
package main
import (
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/plugins/migratecmd"
// _ "myapp/migrations" ← omitted: migrations never run
)
Correct (register migratecmd, import migrations package):
// main.go
package main
import (
"log"
"os"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/plugins/migratecmd"
"github.com/pocketbase/pocketbase/tools/osutils"
// Import side-effects only; this registers all init() migrations.
_ "myapp/migrations"
)
func main() {
app := pocketbase.New()
migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{
// Automigrate generates a new .go file whenever you make
// collection changes in the Dashboard (dev-only).
Automigrate: osutils.IsProbablyGoRun(),
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}
Create and write a migration:
# Create a blank migration file in ./migrations/
go run . migrate create "add_summary_to_posts"
// migrations/1687801090_add_summary_to_posts.go
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
// app is a transactional App instance – safe to use directly.
collection, err := app.FindCollectionByNameOrId("posts")
if err != nil {
return err
}
collection.Fields.Add(&core.TextField{
Name: "summary",
Required: false,
})
return app.Save(collection)
}, func(app core.App) error {
// Optional rollback
collection, err := app.FindCollectionByNameOrId("posts")
if err != nil {
return err
}
collection.Fields.RemoveByName("summary")
return app.Save(collection)
})
}
Snapshot all collections (useful for a fresh repo):
# Generates a migration file that recreates your current schema from scratch.
go run . migrate collections
Clean up dev migration history:
# Remove _migrations table entries that have no matching .go file.
# Run after squashing or deleting intermediate dev migration files.
go run . migrate history-sync
Apply / roll back manually:
go run . migrate up # apply all unapplied migrations
go run . migrate down 1 # revert the last applied migration
Key details:
- Migration functions receive a transactional
core.App– treat it as the database source of truth. Never use the outerappvariable inside migration callbacks. - New unapplied migrations run automatically on every
servestart – no manual step in production. Automigrate: osutils.IsProbablyGoRun()limits auto-generation togo run(development) and prevents accidental file creation in production binaries.- Prefer the collection API (
app.Save(collection)) over raw SQLALTER TABLEso PocketBase's internal schema cache stays consistent. - Commit all generated
.gofiles to version control; do not commitpb_data/.
Reference: Extend with Go – Migrations