---
title: Migrations Overview
description: Modular, Go-code migrations with dependency-aware ordering.
---

Grove migrations are Go code, not SQL files. Any Go module can register migrations into named groups with dependency-aware ordering. Forge extensions ship their own migrations that compose automatically.

## Key Concepts

- **Migrations are Go functions** — not SQL files, enabling logic, conditionals, and data transformations
- **Groups** — Migrations belong to named groups (e.g., `"core"`, `"forge.billing"`)
- **Dependencies** — Groups declare dependencies on other groups for ordering
- **Topological Sort** — The migrator resolves execution order automatically
- **Locking** — Advisory locks prevent concurrent migration runs

## Defining Migrations

```go
import "github.com/xraph/grove/migrate"

var Migrations = migrate.NewGroup("core")

func init() {
    Migrations.MustRegister(&migrate.Migration{
        Name:    "create_users",
        Version: "20240101000000",
        Up: func(ctx context.Context, exec migrate.Executor) error {
            _, err := exec.NewCreateTable((*User)(nil)).
                IfNotExists().
                Exec(ctx)
            return err
        },
        Down: func(ctx context.Context, exec migrate.Executor) error {
            _, err := exec.NewDropTable((*User)(nil)).
                IfExists().
                Exec(ctx)
            return err
        },
    })
}
```

## Running Migrations

```go
import "github.com/xraph/grove/drivers/pgdriver/pgmigrate"

// Create a driver-specific executor
pgdb := pgdriver.Unwrap(db)
executor := pgmigrate.NewExecutor(pgdb)

// Create the orchestrator with the executor
orchestrator := migrate.NewOrchestrator(executor,
    core.Migrations,
    billing.Migrations,
)

// Migrate up
result, err := orchestrator.Migrate(ctx)
fmt.Printf("Applied %d migrations\n", len(result.Applied))

// Rollback last migration
result, err = orchestrator.Rollback(ctx)

// Check status
statuses, err := orchestrator.Status(ctx)
```

## Migration Table

Grove tracks migration state in `grove_migrations`:

```sql
CREATE TABLE grove_migrations (
    id         SERIAL PRIMARY KEY,
    group_name VARCHAR(255) NOT NULL,
    name       VARCHAR(255) NOT NULL,
    version    VARCHAR(255) NOT NULL,
    batch      INTEGER NOT NULL,
    applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    UNIQUE(group_name, version)
);
```

## Executor Registry

The executor registry provides driver-agnostic executor creation through auto-registration. Each driver's migrate package registers its factory via `init()`, so a blank import is all you need.

### Auto-Registration

Import the driver's migrate package to register its executor factory:

| Import | Registered Name |
|--------|----------------|
| `_ "github.com/xraph/grove/drivers/pgdriver/pgmigrate"` | `pg` |
| `_ "github.com/xraph/grove/drivers/mysqldriver/mysqlmigrate"` | `mysql` |
| `_ "github.com/xraph/grove/drivers/sqlitedriver/sqlitemigrate"` | `sqlite` |
| `_ "github.com/xraph/grove/drivers/tursodriver/tursomigrate"` | `turso` |
| `_ "github.com/xraph/grove/drivers/clickhousedriver/clickhousemigrate"` | `clickhouse` |
| `_ "github.com/xraph/grove/drivers/mongodriver/mongomigrate"` | `mongo` |

### Using the Registry

Once registered, create executors from any driver dynamically:

```go
import "github.com/xraph/grove/migrate"

executor, err := migrate.NewExecutorFor(db.Driver())
if err != nil {
    // No factory registered for this driver
    log.Fatal(err)
}

orch := migrate.NewOrchestrator(executor, core.Migrations)
```

### Manual Registration

Register a custom executor factory for drivers not included in Grove:

```go
migrate.RegisterExecutor("custom", func(drv any) migrate.Executor {
    return myCustomExecutor(drv)
})
```

### Listing Registered Executors

```go
names := migrate.Executors()
fmt.Println("Registered executors:", names) // e.g. [pg mysql sqlite]
```

<Callout type="info">
When using the Grove Forge extension, executor registration is handled automatically — you only need the blank import for your driver's migrate package.
</Callout>

