> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/pocketbase/pocketbase/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrations

> Learn how to manage database schema changes with PocketBase migrations

PocketBase uses a migration system to track and apply database schema changes. Migrations ensure that database modifications are versioned, reproducible, and can be rolled back if needed.

## Understanding migrations

PocketBase has two types of migrations:

* **System migrations**: Built into PocketBase core, these handle framework schema updates
* **App migrations**: User-defined migrations for your application's collections and data

Migrations are stored in the `_migrations` table and executed in order based on their filename.

## Migration file structure

Migrations can be written in JavaScript or Go:

### JavaScript migrations

Located in `pb_migrations/` by default:

```javascript theme={null}
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
  // Add up queries...
  const collection = app.findCollectionByNameOrId("posts");
  collection.fields.addAt(0, new Field({
    "name": "title",
    "type": "text",
    "required": true
  }));
  
  return app.save(collection);
}, (app) => {
  // Add down queries...
  const collection = app.findCollectionByNameOrId("posts");
  collection.fields.removeById("field_id");
  
  return app.save(collection);
});
```

### Go migrations

Located in `migrations/` by default:

```go theme={null}
package migrations

import (
	"github.com/pocketbase/pocketbase/core"
	m "github.com/pocketbase/pocketbase/migrations"
)

func init() {
	m.Register(func(app core.App) error {
		// Add up queries...
		collection, err := app.FindCollectionByNameOrId("posts")
		if err != nil {
			return err
		}
		
		// Add field
		field := &core.TextField{
			Name:     "title",
			Required: true,
		}
		collection.Fields.Add(field)
		
		return app.Save(collection)
	}, func(app core.App) error {
		// Add down queries...
		collection, err := app.FindCollectionByNameOrId("posts")
		if err != nil {
			return err
		}
		
		collection.Fields.RemoveById("field_id")
		
		return app.Save(collection)
	})
}
```

## Setting up migrations

Enable migrations in your PocketBase application:

```go theme={null}
package main

import (
	"log"
	
	"github.com/pocketbase/pocketbase"
	"github.com/pocketbase/pocketbase/plugins/migratecmd"
)

func main() {
	app := pocketbase.New()
	
	// Register the migrate command
	migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{
		TemplateLang: migratecmd.TemplateLangJS, // or TemplateLangGo
		Automigrate:  true,
		Dir:          "./pb_migrations", // custom migrations directory
	})
	
	if err := app.Start(); err != nil {
		log.Fatal(err)
	}
}
```

### Configuration options

* **TemplateLang**: `migratecmd.TemplateLangJS` or `migratecmd.TemplateLangGo` (default: Go)
* **Automigrate**: Enable automatic migration generation on collection changes (default: false)
* **Dir**: Custom migrations directory (default: `pb_migrations/` for JS, `migrations/` for Go)

## Creating migrations

### Manual migration creation

Create a blank migration template:

```bash theme={null}
./pocketbase migrate create add_user_bio
```

This generates a file like `1234567890_add_user_bio.js`:

```javascript theme={null}
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
  // add up queries...
}, (app) => {
  // add down queries...
});
```

### Collections snapshot

Generate a migration with the current collections state:

```bash theme={null}
./pocketbase migrate collections
```

This creates a migration like `1234567890_collections_snapshot.js`:

```javascript theme={null}
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
  const snapshot = [
    {
      "id": "abc123",
      "name": "users",
      "type": "auth",
      "fields": [
        {
          "id": "field_abc",
          "name": "username",
          "type": "text",
          "required": true
        }
      ]
    }
  ];

  return app.importCollections(snapshot, false);
}, (app) => {
  return null;
});
```

### Automigrations

When `Automigrate: true` is enabled, PocketBase automatically creates migrations when you modify collections through the admin UI.

For example, creating a new collection generates:

```javascript theme={null}
// 1234567890_created_posts.js
migrate((app) => {
  const collection = new Collection({
    "name": "posts",
    "type": "base",
    "fields": [
      {
        "name": "title",
        "type": "text"
      }
    ]
  });

  return app.save(collection);
}, (app) => {
  const collection = app.findCollectionByNameOrId("collection_id");
  return app.delete(collection);
});
```

<Info>
  Automigrations are prefixed with action names: `created_`, `updated_`, or `deleted_`.
</Info>

## Applying migrations

### Apply all pending migrations

```bash theme={null}
./pocketbase migrate up
```

Output:

```
Applied 1234567890_add_user_bio.js
Applied 1234567891_create_posts.js
No new migrations to apply.
```

### Migration execution process

From `core/migrations_runner.go:122-173`, the `Up()` method:

<Steps>
  <Step title="Initialize migrations table">
    Creates the `_migrations` table if it doesn't exist.
  </Step>

  <Step title="Check applied status">
    Queries the `_migrations` table to see if each migration has been applied.
  </Step>

  <Step title="Execute in transaction">
    Runs unapplied migrations within a database transaction for safety.

    ```go theme={null}
    err := app.RunInTransaction(func(txApp App) error {
        return txApp.AuxRunInTransaction(func(txApp App) error {
            // Execute migration Up function
            if err := m.Up(txApp); err != nil {
                return fmt.Errorf("failed to apply migration %s: %w", m.File, err)
            }
            return nil
        })
    })
    ```
  </Step>

  <Step title="Record migration">
    Saves the migration record with timestamp to `_migrations` table.
  </Step>
</Steps>

<Warning>
  Migrations run in both the main database and auxiliary database transactions. Any error will rollback all changes.
</Warning>

## Reverting migrations

### Revert last migration

```bash theme={null}
./pocketbase migrate down
```

This prompts for confirmation:

```
1234567891_create_posts.js
Do you really want to revert the last 1 applied migration(s)? (y/N)
```

### Revert multiple migrations

```bash theme={null}
./pocketbase migrate down 3
```

Reverts the last 3 migrations.

### Revert all migrations

```bash theme={null}
./pocketbase migrate down -1
```

<Warning>
  Reverting migrations can cause data loss if the down function drops collections or fields. Always back up before reverting.
</Warning>

## Migration best practices

<Accordion title="Write reversible migrations">
  Always implement both `up` and `down` functions:

  ```javascript theme={null}
  migrate((app) => {
    // Up: Add field
    const collection = app.findCollectionByNameOrId("users");
    collection.fields.addAt(0, new Field({
      "name": "bio",
      "type": "text"
    }));
    return app.save(collection);
  }, (app) => {
    // Down: Remove field
    const collection = app.findCollectionByNameOrId("users");
    const field = collection.fields.getByName("bio");
    collection.fields.removeById(field.id);
    return app.save(collection);
  });
  ```
</Accordion>

<Accordion title="Use descriptive migration names">
  Good migration names describe the change:

  * ✅ `add_user_bio_field.js`
  * ✅ `create_comments_collection.js`
  * ✅ `migrate_old_posts_to_new_schema.js`
  * ❌ `migration1.js`
  * ❌ `update.js`
</Accordion>

<Accordion title="Test migrations in development first">
  Before applying to production:

  1. Create migration in development
  2. Test `migrate up`
  3. Verify application functionality
  4. Test `migrate down`
  5. Test `migrate up` again
  6. Commit migration file
  7. Deploy to production
</Accordion>

<Accordion title="Keep migrations small and focused">
  Create separate migrations for different changes:

  ```bash theme={null}
  # Good: Separate migrations
  ./pocketbase migrate create add_user_avatar
  ./pocketbase migrate create add_post_tags

  # Avoid: One large migration doing everything
  ./pocketbase migrate create update_schema
  ```
</Accordion>

<Accordion title="Handle data migrations carefully">
  When migrating data, handle edge cases:

  ```javascript theme={null}
  migrate((app) => {
    const posts = app.findCollectionByNameOrId("posts");
    
    // Fetch all records
    const records = $app.findRecordsByFilter(
      "posts",
      "", // no filter
      "-created",
      500
    );
    
    // Update each record
    records.forEach((record) => {
      // Handle null values
      if (!record.get("old_field")) {
        record.set("new_field", "default_value");
      } else {
        record.set("new_field", record.get("old_field"));
      }
      app.save(record);
    });
    
    return null;
  }, (app) => {
    // Revert data changes
    return null; // or implement reverse migration
  });
  ```
</Accordion>

## Advanced migration techniques

### Conditional migrations with ReapplyCondition

From `core/migrations_list.go:9-14`, migrations support conditional reapplication:

```go theme={null}
type Migration struct {
	Up               func(txApp App) error
	Down             func(txApp App) error
	File             string
	ReapplyCondition func(txApp App, runner *MigrationsRunner, fileName string) (bool, error)
}
```

Use case: Reapply a migration when certain conditions change:

```go theme={null}
m.Register(func(app core.App) error {
	// Migration logic
	return nil
}, func(app core.App) error {
	// Rollback logic
	return nil
})

// Add reapply condition
lastMigration := appMigrations.Item(appMigrations.Items().Len() - 1)
lastMigration.ReapplyCondition = func(txApp core.App, runner *core.MigrationsRunner, fileName string) (bool, error) {
	// Reapply if a specific setting changed
	return txApp.Settings().Meta.AppName != "expected_name", nil
}
```

### Programmatic migration execution

Run migrations from Go code:

```go theme={null}
import (
	"github.com/pocketbase/pocketbase/core"
)

// Create migrations list
var list core.MigrationsList
list.Copy(core.SystemMigrations)
list.Copy(core.AppMigrations)

// Create runner
runner := core.NewMigrationsRunner(app, list)

// Apply all migrations
applied, err := runner.Up()
if err != nil {
	log.Fatal(err)
}

for _, file := range applied {
	log.Printf("Applied %s", file)
}

// Revert migrations
reverted, err := runner.Down(2) // Revert last 2
if err != nil {
	log.Fatal(err)
}
```

### Custom migrations table

Change the default migrations table name:

```go theme={null}
runner := core.NewMigrationsRunner(app, list)
runner.tableName = "custom_migrations"
```

## Migration hooks

You can hook into migration events, though this is typically done through the migration files themselves. The migration system triggers events during execution:

```go theme={null}
// Migrations run inside these hooks automatically
app.OnBeforeServe().BindFunc(func(e *core.ServeEvent) error {
	// System migrations are applied here
	return e.Next()
})
```

## Troubleshooting

### Migration already applied

**Issue**: Migration shows as applied but changes aren't visible.

**Solution**: Check the `_migrations` table:

```sql theme={null}
SELECT * FROM _migrations ORDER BY applied DESC;
```

Remove the entry to reapply:

```sql theme={null}
DELETE FROM _migrations WHERE file = '1234567890_migration_name.js';
```

Then run `migrate up` again.

### Migration file not found

**Issue**: `_migrations` table references files that don't exist.

**Solution**: Sync the migrations table:

```bash theme={null}
./pocketbase migrate history-sync
```

This removes references to deleted migration files.

### Transaction deadlock

**Issue**: Migration times out or deadlocks.

**Solution**:

* Ensure migrations don't have long-running operations
* Split large data migrations into smaller batches
* Avoid external API calls in migrations

### Import errors in Go migrations

**Issue**: `undefined: Collection` or similar errors.

**Solution**: Import the correct packages:

```go theme={null}
import (
	"encoding/json"
	"github.com/pocketbase/pocketbase/core"
	m "github.com/pocketbase/pocketbase/migrations"
)
```

## Next steps

<CardGroup cols={2}>
  <Card title="Going to production" icon="rocket" href="/deployment/going-to-production">
    Learn production deployment best practices
  </Card>

  <Card title="Backups" icon="database" href="/deployment/backups">
    Set up automated backups for your data
  </Card>
</CardGroup>
