Schema migrations and versioning for PocketBase. Use when creating migrations, managing schema versions, syncing collections between environments, using automig
复制下面这句话,粘贴给 Claude Code、Codex、Cursor 等 AI 编程工具,它会读取安装说明并在你确认后完成安装。
请阅读 https://ai.atlankj.com/install/asset/gh-pb-migrations-4eb380003bb4 ,按照其中的说明把「PocketBase Migrations」安装到你(当前 AI 工具)中。执行前先告诉我将运行的命令和写入的位置,等我确认。
查看 AI 将读取的安装说明正在读取 GitHub 原文…
内容来自 GitHub 原始文件,由原作者维护。在 GitHub 查看
PocketBase supports two approaches to schema management:
pb_migrations/# Create a new empty migration file
./pocketbase migrate create "add_posts_collection"
# Creates: pb_migrations/1234567890_add_posts_collection.js
# Apply all pending migrations
./pocketbase migrate up
# Revert the last applied migration
./pocketbase migrate down
# Generate a full snapshot of all current collections
./pocketbase migrate collections
# Creates a migration file that recreates all collections from scratch
# Sync migration history with actual DB state (mark all as applied)
./pocketbase migrate history-sync
Enabled by default. When you change collections in the Dashboard, PocketBase auto-generates migration files in pb_migrations/.
# Start with auto-migrate (default)
./pocketbase serve
# Disable auto-migrate (production)
./pocketbase serve --automigrate=0
Workflow:
pb_migrations/serve start--automigrate=0 to prevent Dashboard changes from generating new migrations// pb_migrations/1234567890_add_posts_collection.js
migrate(
// UP — apply migration
function(app) {
var collection = new Collection({
name: "posts",
type: "base",
fields: [
{ name: "title", type: "text", required: true },
{ name: "body", type: "editor" },
{ name: "author", type: "relation", collectionId: "USERS_COLLECTION_ID", cascadeDelete: false, maxSelect: 1, required: true },
{ name: "status", type: "select", values: ["draft", "published", "archived"] },
{ name: "published_at", type: "date" },
{ name: "tags", type: "relation", collectionId: "TAGS_COLLECTION_ID", maxSelect: 0 }
],
indexes: [
"CREATE INDEX idx_posts_author ON posts (author)",
"CREATE INDEX idx_posts_status ON posts (status)",
"CREATE UNIQUE INDEX idx_posts_title ON posts (title)"
],
listRule: "", // WARNING: "" means public access — use a filter or null to restrict
viewRule: "", // WARNING: "" means public access — use a filter or null to restrict
createRule: "@request.auth.id != ''",
updateRule: "author = @request.auth.id",
deleteRule: "author = @request.auth.id"
})
app.save(collection)
},
// DOWN — revert migration
function(app) {
var collection = app.findCollectionByNameOrId("posts")
app.delete(collection)
}
)
Important: the app inside migrations is a transactional instance. If any error occurs, the entire migration is rolled back.
var collection = new Collection({
name: "posts",
type: "base",
fields: [
{ name: "title", type: "text", required: true, min: 3, max: 200 },
{ name: "slug", type: "text", required: true, autogenerate: { pattern: "slugify(title)" } },
{ name: "body", type: "editor" },
{ name: "cover", type: "file", maxSelect: 1, maxSize: 5242880, mimeTypes: ["image/jpeg", "image/png", "image/webp"] },
{ name: "views", type: "number", min: 0 },
{ name: "metadata", type: "json", maxSize: 2000000 },
{ name: "featured", type: "bool" },
{ name: "published_at", type: "date" }
]
})
app.save(collection)
var collection = new Collection({
name: "users",
type: "auth",
fields: [
{ name: "name", type: "text", required: true },
{ name: "avatar", type: "file", maxSelect: 1, maxSize: 5242880 },
{ name: "role", type: "select", values: ["user", "editor", "admin"], required: true }
],
passwordAuth: { enabled: true, identityFields: ["email", "username"] },
oauth2: { enabled: true },
otp: { enabled: false },
mfa: { enabled: false },
authToken: { duration: 604800 } // 7 days
})
app.save(collection)
var collection = new Collection({
name: "posts_stats",
type: "view",
viewQuery: "SELECT p.id, p.title, COUNT(c.id) as comments_count, p.views FROM posts p LEFT JOIN comments c ON c.post = p.id GROUP BY p.id",
listRule: "",
viewRule: ""
})
app.save(collection)
migrate(function(app) {
var collection = app.findCollectionByNameOrId("posts")
// Add a new field
collection.fields.add({
name: "subtitle",
type: "text",
max: 500
})
// Remove a field
collection.fields.removeByName("old_field")
// Update API rules
collection.listRule = "@request.auth.id != ''"
collection.viewRule = ""
// Add index
collection.indexes.push("CREATE INDEX idx_posts_subtitle ON posts (subtitle)")
app.save(collection)
}, function(app) {
var collection = app.findCollectionByNameOrId("posts")
collection.fields.removeByName("subtitle")
app.save(collection)
})
migrate(function(app) {
app.db().newQuery("ALTER TABLE posts ADD COLUMN legacy_id TEXT DEFAULT ''").execute()
app.db().newQuery("UPDATE posts SET legacy_id = id WHERE legacy_id = ''").execute()
}, function(app) {
app.db().newQuery("ALTER TABLE posts DROP COLUMN legacy_id").execute()
})
Warning: raw SQL bypasses PocketBase's schema cache. Run migrate collections afterward to re-sync if needed.
onBootstrap(function(e) {
var settings = e.app.settings()
settings.meta.appName = "My App"
settings.meta.appURL = "https://myapp.com"
settings.meta.senderName = "My App"
settings.meta.senderAddress = "noreply@myapp.com"
settings.smtp.enabled = true
settings.smtp.host = "smtp.example.com"
settings.smtp.port = 587
settings.smtp.username = $os.getenv("SMTP_USER")
settings.smtp.password = $os.getenv("SMTP_PASS")
e.app.save(settings)
return e.next()
})
migrate(function(app) {
var superusers = app.findCollectionByNameOrId("_superusers")
var record = new Record(superusers)
// IMPORTANT: always set PB_ADMIN_EMAIL and PB_ADMIN_PASSWORD env vars
var email = $os.getenv("PB_ADMIN_EMAIL")
var password = $os.getenv("PB_ADMIN_PASSWORD")
if (!email || !password) {
throw new Error("PB_ADMIN_EMAIL and PB_ADMIN_PASSWORD env vars are required")
}
record.set("email", email)
record.set("password", password)
app.save(record)
})
./pocketbase migrate collections generates a complete snapshot — useful for:
The generated file uses app.importCollections(collections) which supports two modes:
app.importCollections(collections, true) — deletes collections/fields not in the snapshot_migrations TablePocketBase tracks applied migrations in the internal _migrations table:
id — auto-generatedfile — migration filenameapplied — timestampmigrate history-sync marks all existing migration files as applied without running them — useful when importing an existing database.
--automigrate=0, migrations run on startupmigrate collections periodically to snapshot current state for documentationonBootstrap, make the seed logic idempotent (existence checks/upserts) because bootstrap runs on every app start