
PocketBase Migrations
FreeManage schema migrations and versioning for PocketBase.
Free · Opens the source repo
What PocketBase Migrations does
PocketBase Migrations is a tool designed to facilitate schema management for PocketBase applications. It supports both automatic and manual migration strategies, allowing developers to choose the approach that best fits their workflow. With auto-migrate enabled by default, changes made through the PocketBase Dashboard automatically generate migration files, which are stored in the pb_migrations/ directory. This feature is particularly useful during the development phase, as it streamlines the process of managing schema changes without requiring manual intervention.
For those who prefer more control over their migrations, the manual migration feature allows developers to write migration files by hand. This gives them the flexibility to customize their migrations according to specific requirements. The command-line interface (CLI) provides several commands to create, apply, and revert migrations, making it easy to manage the schema versioning throughout the development lifecycle. The migration file format is straightforward, enabling developers to define the structure of their collections and the rules governing data access and manipulation.
The skill also includes functionality for syncing migration history with the actual database state, ensuring that the migration tracking table accurately reflects the applied migrations. This is crucial for maintaining consistency between different environments, such as development and production. Additionally, developers can create collections programmatically, allowing for dynamic schema changes based on application logic. The ability to modify existing collections and execute raw SQL commands within migrations adds further versatility to the skill, catering to advanced use cases.
Overall, PocketBase Migrations is an essential tool for developers working with PocketBase who need to manage schema changes efficiently and effectively. It is particularly beneficial for teams that require a robust migration strategy to ensure their applications evolve smoothly over time.
When to use it
Use this skill when developing PocketBase applications that require consistent schema management and version control.
When not to use it
This skill may not be suitable for projects that do not utilize PocketBase or for developers who prefer a different database management system.
What you can build with it
Developing with Auto-migrate
Use the auto-migrate feature during development to automatically generate migration files as you make changes in the Dashboard.
Manual Migration Control
For precise control over schema changes, write migration files manually to customize the structure and rules of your collections.
Syncing Database State
Utilize the history-sync command to ensure that your migration history accurately reflects the current state of your database.
How to install PocketBase Migrations
View source1. Install with the skills CLI
npx skills add davila7/claude-code-templates/pb-migrations --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by davila7PocketBase Migrations & Schema Versioning
Overview
PocketBase supports two approaches to schema management:
- Auto-migrate (default in dev) — Dashboard changes auto-generate migration files in
pb_migrations/ - Manual migrations — write migration files by hand for full control
CLI Commands
# 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
Auto-migrate Mode
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:
- Develop with auto-migrate ON — use Dashboard to design schema
- Migration files are auto-generated in
pb_migrations/ - Commit these files to git
- Deploy: migrations run automatically on
servestart - In production: use
--automigrate=0to prevent Dashboard changes from generating new migrations
Migration File Format
// 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.
Creating Collections Programmatically
Base collection
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)
Auth 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)
View 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)
Modifying Existing Collections
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)
})
Raw SQL in Migrations
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.
Settings & Superuser in Migrations
Initialize app settings
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()
})
Create superuser in migration
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)
})
Snapshot Migrations
./pocketbase migrate collections generates a complete snapshot — useful for:
- Bootstrapping a new environment
- Resetting migration history
- Reviewing full schema in one file
The generated file uses app.importCollections(collections) which supports two modes:
- Default (merge/extend): adds new collections and fields, updates existing ones, doesn't delete anything
- Delete missing:
app.importCollections(collections, true)— deletes collections/fields not in the snapshot
_migrations Table
PocketBase tracks applied migrations in the internal _migrations table:
id— auto-generatedfile— migration filenameapplied— timestamp
migrate history-sync marks all existing migration files as applied without running them — useful when importing an existing database.
Best Practices
- Dev: use auto-migrate + Dashboard for schema design, commit generated files
- Staging/Prod: deploy with
--automigrate=0, migrations run on startup - Always write DOWN migrations — reversibility saves you when things go wrong
- One concern per migration — don't mix unrelated schema changes
- Test migrations: apply on a copy of production data before deploying
- Use
migrate collectionsperiodically to snapshot current state for documentation - Never edit applied migrations — create a new migration to fix issues
- Seed data: prefer a dedicated migration for one-time initial data; if using
onBootstrap, make the seed logic idempotent (existence checks/upserts) because bootstrap runs on every app start
Frequently asked questions about PocketBase Migrations
Similar skills
ClickHouse Logs Queries
Efficiently manage Supabase logs with ClickHouse SQL.
EF Core D2 Database Diagram Generator
Visualize your EF Core models as D2 diagrams effortlessly.
Safe SQL Execution
Ensure secure SQL execution in Supabase applications.
Oracle to PostgreSQL Migration
Identify migration risks between Oracle and PostgreSQL.
SSMA Console
Streamline Oracle to SQL Server migrations with ease.
SQL Performance Optimization
Enhance SQL query efficiency across all databases.
