Claude Code SkillPocketbase

Pb Migrations

"Schema migrations and versioning for PocketBase. Use when creating migrations, managing schema versions, syncing collections between environments, using automigrate, or creating collections programmatically. Covers migrate commands, migration file format, snapshot imports, and the _migrations tracking table."

Install with the Claude Code Templates CLI
$ npx claude-code-templates@latest --skill="pocketbase/pb-migrations" --yes

Requires Claude Code. The command adds this skill to your project's .claudedirectory — nothing runs on ToolZip's servers.

What's inside this skill

Component source

PocketBase 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 serve start
  • In production: use --automigrate=0 to 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-generated
  • file — migration filename
  • applied — 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 collections periodically 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
Type
Skill
Category
Pocketbase
Installs
Source
GitHub ↗

Related Claude Code Skills

SkillPocketbase

Pb Sdk

"JavaScript SDK usage for PocketBase client applications. Use when calling PocketBase from frontend or Node.js, authenticating users, subscribing to realtime events, uploading files, or working with the PocketBase JS/TS SDK. Covers CRUD, auth flows, authStore, realtime SSE, file handling, batch operations, and query syntax."

1 installsView →
SkillPocketbase

Pb Api Rules

"API rules and filter expressions for PocketBase access control. Use when setting permissions, writing filter expressions, configuring who can access what, or debugging 403/404 responses. Covers all 5 rule types, filter syntax, operators, request/collection macros, and field modifiers."

View →
SkillPocketbase

Pb Collections

"Collection and schema design for PocketBase. Use when creating collections, designing schemas, adding fields, setting up relations, or choosing between base/auth/view collection types. Prevents wrong field types, documents zero-default behavior, and covers relation cascading."

View →
SkillPocketbase

Pb Deploy

"Production deployment for PocketBase. Use when deploying PocketBase to a server, setting up Docker, configuring systemd, reverse proxy (nginx/Caddy), TLS, SMTP, backups, S3 storage, rate limiting, or hardening for production. Provides ready-to-use configs."

View →
SkillPocketbase

Pb Hooks

"Server-side JavaScript hooks for PocketBase (pb_hooks). Use when writing custom routes, event hooks, cron jobs, sending emails, making HTTP requests, querying the database, or extending PocketBase with server-side logic. Covers the goja ES5 runtime, routing, middleware, all event hooks, DB queries, record operations, and global APIs."

View →
SkillCreative Design

Frontend Design

Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.

4.2k installsView →

Catalog data and component content are sourced from the open-source davila7/claude-code-templates project (MIT license). ToolZip curates the listing and writes original descriptions; every component links back to its original source. Claude Code is a product of Anthropic. ToolZip is an independent catalog and is not affiliated with or endorsed by Anthropic.