
Pocketbase Migrations
- 3 installs
- 4 repo stars
- Updated January 13, 2026
- knowsuchagency/pocketbase-template
Helps with ai & agent building tasks.
About
pocketbase-migrations is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- pocketbase-migrations
- AI & Agent Building
- AI-coding skill
Pocketbase Migrations by the numbers
- 3 all-time installs (skills.sh)
- Ranked #13,657 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/knowsuchagency/pocketbase-template --skill pocketbase-migrationsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3 |
|---|---|
| repo stars | ★ 4 |
| Last updated | January 13, 2026 |
| Repository | knowsuchagency/pocketbase-template ↗ |
What it does
Helps with ai & agent building tasks.
Files
PocketBase Migrations
Guide for creating PocketBase migrations using the Go-based system (0.20+).
Core Workflow
Critical Migration Pattern
ALWAYS follow this workflow:
1. Write ONE migration at a time 2. Execute immediately with mise run migrate 3. Verify it worked with mise run show-collections 4. Only then write the next migration 5. 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
1. Create collections in dependency order 2. Fetch collections before creating relations 3. Use actual collection IDs for relations 4. 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:
1. Independent collections (no relations) 2. Collections depending on system collections 3. Collections with relations to other custom collections 4. 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-referencingCollection Rules
Rule Types
ListRule- List recordsViewRule- View individual recordsCreateRule- Create recordsUpdateRule- Update recordsDeleteRule- 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 filemise run migrate- Run pending migrationsmise run migratedown- Rollback last migrationmise run show-collections- Display collectionsmise run backup- Backup database to /tmp
Best Practices
1. Import types package for nullable values: "github.com/pocketbase/pocketbase/tools/types" 2. Use `types.Pointer()` for rule assignments and pointer values 3. Check collection existence before creating relations 4. Validate field names don't conflict with system fields (id, created, updated) 5. Handle errors immediately after operations 6. Never skip migration testing - run each one before writing the next 7. Backup before destructive changes
Common Gotchas
- Use
MaxSelect(notMax) for RelationField - Use
MaxSize(notMax) 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/
PocketBase Field Types Reference
Complete reference for all PocketBase field types and their configurations.
Text Fields
&core.TextField{
Name: "title",
Required: true,
Min: 1, // minimum character length
Max: 100, // maximum character length
Pattern: "^[a-z]+$", // regex validation (optional)
}Common use cases:
- Titles, names, short descriptions
- Slugs with pattern validation
- Any text with length constraints
Number Fields
&core.NumberField{
Name: "price",
Required: true,
Min: types.Pointer(0.0), // use types.Pointer for min
Max: types.Pointer(999.99), // use types.Pointer for max
OnlyInt: false, // restrict to integers only
NoDecimal: false, // allow decimal values
}Important notes:
- Use
types.Pointer()for min/max values - Set
OnlyInt: truefor integer-only fields - Default allows decimal values
Common use cases:
- Prices, quantities, ratings
- Counts, scores, measurements
- Any numeric data
Boolean Fields
&core.BoolField{
Name: "isActive",
Required: false,
}Common use cases:
- Feature flags, status indicators
- Toggles, checkboxes
- Yes/no fields
Email Fields
&core.EmailField{
Name: "email",
Required: true,
}Common use cases:
- Contact emails
- User emails (in non-auth collections)
- Any email with validation
URL Fields
&core.URLField{
Name: "website",
OnlyDomain: false, // true = require domain only, no path
}Common use cases:
- Website links
- Social media profiles
- External references
Date Fields
Manual Date Field
&core.DateField{
Name: "publishedAt",
Min: types.Pointer(time.Now()),
Max: types.Pointer(time.Now().AddDate(1, 0, 0)),
}Common use cases:
- Event dates, deadlines
- Birth dates, expiration dates
- Any user-entered date
Auto-managed Date Field
&core.AutodateField{
Name: "created",
OnCreate: true, // set when record is created
OnUpdate: false, // don't update on record updates
}
&core.AutodateField{
Name: "modified",
OnCreate: false,
OnUpdate: true, // update when record is modified
}Common use cases:
- Creation timestamps
- Last modified timestamps
- Automated tracking fields
Select Fields
Single Select
&core.SelectField{
Name: "status",
Required: true,
Values: []string{"draft", "published", "archived"},
MaxSelect: 1, // cannot exceed number of values
}Multi-select
&core.SelectField{
Name: "tags",
Values: []string{"tech", "design", "business", "marketing"},
MaxSelect: 3, // allow up to 3 selections
}Common use cases:
- Status fields, categories
- Tags, labels
- Any predefined options
File Fields
&core.FileField{
Name: "avatar",
MaxSelect: 1, // number of files
MaxSize: 5242880, // 5MB in bytes
MimeTypes: []string{"image/jpeg", "image/png"}, // allowed types
Protected: false, // require auth to access
}File size reference:
- 1MB = 1,048,576 bytes
- 5MB = 5,242,880 bytes
- 10MB = 10,485,760 bytes
Common mime types:
- Images:
"image/jpeg","image/png","image/gif","image/webp" - Documents:
"application/pdf","application/msword" - Archives:
"application/zip","application/x-rar-compressed"
Common use cases:
- Profile pictures, thumbnails
- Document uploads
- Media attachments
Relation Fields
// Single relation
authorsCollection, err := app.FindCollectionByNameOrId("authors")
if err != nil {
return err
}
&core.RelationField{
Name: "author",
Required: true,
CollectionId: authorsCollection.Id,
MaxSelect: 1, // Note: MaxSelect, not Max
CascadeDelete: true, // delete related records when this is deleted
}
// Multiple relations
&core.RelationField{
Name: "categories",
CollectionId: categoriesCollection.Id,
MaxSelect: 5, // allow up to 5 selections
}
// System users relation
&core.RelationField{
Name: "creator",
CollectionId: "_pb_users_auth_", // system users collection
MaxSelect: 1,
}Important notes:
- Use
MaxSelect, notMax - Fetch target collection first to get its ID
- System users collection:
"_pb_users_auth_" CascadeDelete: trueremoves related records
Common use cases:
- User associations
- Category/tag relationships
- Parent-child relationships
- Many-to-many links
JSON Fields
&core.JSONField{
Name: "metadata",
MaxSize: 65535, // in bytes
}Common use cases:
- Flexible metadata
- Configuration objects
- Dynamic structured data
Editor Fields (Rich Text)
&core.EditorField{
Name: "content",
Required: true,
ConvertURLs: true, // convert URLs to links
MaxSize: 1048576, // Note: MaxSize, not Max (1MB)
}Size reference:
- 100KB = 102,400 bytes
- 1MB = 1,048,576 bytes
- 5MB = 5,242,880 bytes
Important notes:
- Use
MaxSize, notMax - Stores rich text/HTML content
ConvertURLs: trueauto-links URLs
Common use cases:
- Blog post content
- Article bodies
- Rich text descriptions
Field Validation Patterns
Text Patterns (Regex)
// Alphanumeric only
Pattern: "^[a-zA-Z0-9]+$"
// Lowercase letters only
Pattern: "^[a-z]+$"
// URL slug
Pattern: "^[a-z0-9-]+$"
// Phone number (US)
Pattern: "^\\d{3}-\\d{3}-\\d{4}$"
// Hex color
Pattern: "^#[0-9A-Fa-f]{6}$"Complete Field Type Examples
Blog Post Collection
collection := core.NewBaseCollection("posts")
collection.Fields.Add(&core.TextField{
Name: "title",
Required: true,
Max: 200,
})
collection.Fields.Add(&core.TextField{
Name: "slug",
Required: true,
Max: 200,
Pattern: "^[a-z0-9-]+$",
})
collection.Fields.Add(&core.EditorField{
Name: "content",
Required: true,
MaxSize: 2097152, // 2MB
})
collection.Fields.Add(&core.SelectField{
Name: "status",
Required: true,
Values: []string{"draft", "published", "archived"},
MaxSelect: 1,
})
collection.Fields.Add(&core.RelationField{
Name: "author",
Required: true,
CollectionId: "_pb_users_auth_",
MaxSelect: 1,
})
collection.Fields.Add(&core.DateField{
Name: "publishedAt",
})
collection.Fields.Add(&core.AutodateField{
Name: "created",
OnCreate: true,
})
collection.Fields.Add(&core.AutodateField{
Name: "updated",
OnUpdate: true,
})E-commerce Product Collection
collection := core.NewBaseCollection("products")
collection.Fields.Add(&core.TextField{
Name: "name",
Required: true,
Max: 200,
})
collection.Fields.Add(&core.TextField{
Name: "description",
Max: 1000,
})
collection.Fields.Add(&core.NumberField{
Name: "price",
Required: true,
Min: types.Pointer(0.0),
})
collection.Fields.Add(&core.NumberField{
Name: "stock",
OnlyInt: true,
Min: types.Pointer(0.0),
})
collection.Fields.Add(&core.FileField{
Name: "images",
MaxSelect: 5,
MaxSize: 5242880, // 5MB per image
MimeTypes: []string{"image/jpeg", "image/png", "image/webp"},
})
collection.Fields.Add(&core.SelectField{
Name: "category",
Required: true,
Values: []string{"electronics", "clothing", "home", "books"},
MaxSelect: 1,
})
collection.Fields.Add(&core.BoolField{
Name: "featured",
})
collection.Fields.Add(&core.JSONField{
Name: "specifications",
MaxSize: 32768, // 32KB
})Field Updates
Adding Fields
collection, err := app.FindCollectionByNameOrId("posts")
if err != nil {
return err
}
collection.Fields.Add(&core.TextField{
Name: "subtitle",
Max: 200,
})
return app.Save(collection)Removing Fields
collection, err := app.FindCollectionByNameOrId("posts")
if err != nil {
return err
}
collection.Fields.RemoveByName("oldField")
return app.Save(collection)Modifying Fields
To modify a field, you need to remove it and re-add it with new settings:
collection, err := app.FindCollectionByNameOrId("posts")
if err != nil {
return err
}
// Remove old field
collection.Fields.RemoveByName("title")
// Add with new settings
collection.Fields.Add(&core.TextField{
Name: "title",
Required: true,
Max: 300, // changed from 200
})
return app.Save(collection)Warning: Removing and re-adding fields may result in data loss. Consider data migration if field contains important data.