# Migrations

> Turn schema changes into versioned migrations and apply them.

_Updated: 2026-06-04_

Your Go schema is the source of truth. Migrations are how that truth becomes
durable, versioned changes to the data store. Railbase generates them by diffing
your DSL against the last applied schema.

## Generate a migration

After you change `schema/`, generate a migration from the diff:

```bash
railbase migrate diff add_posts_status
```

This writes `migrations/NNNN_add_posts_status.up.sql`. User migrations are
numbered from **1000** up (system migrations occupy the lower range). If the diff
is empty you'll see *"schema unchanged — no migration emitted."*

> [!NOTE]
> If a change can't be made safely automatically (for example a type change that
> could lose data), `migrate diff` refuses and tells you to hand-write the
> migration. It also emits a guarded backfill placeholder for `NOT NULL` additions
> so you can't apply an unsafe step by accident.

## Apply migrations

```bash
railbase migrate up            # apply all pending, then snapshot the schema
railbase migrate status        # VERSION · NAME · STATUS(applied|pending) · SOURCE
```

Migrations also **apply automatically on boot**, so a freshly-deployed binary
brings its data store up to date on `serve` without a manual step.

## Rolling back

```text
railbase migrate down   →  not implemented (refuses)
```

There is no automatic down-migration. To reverse a change, write a new forward
migration that undoes it — explicit and reviewable beats a magic rollback. And
before anything risky, take a snapshot:

```bash
railbase backup
```

See [Backups & restore](backups-and-restore).

## Where migrations live

```text
migrations/
├── 1000_initial_schema.up.sql
├── 1001_add_posts_status.up.sql
└── …
```

Commit the `migrations/` directory to source control alongside `schema/` — that
pair is the full, replayable history of your data model.
