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

# Writing migrations

> Scaffold a migration, implement up/down, and order changes with dependencies.

A migration is a class implementing `IMigration`, registered with `@decorator.migration()`. The decorator adds the class to the container and to the migrations registry, so the runner can resolve and order it. `up()` applies the change and `down()` reverses it.

## Scaffold a migration

Generate a timestamped migration class with the CLI. `talos migration:create` writes a migration class under `modules/<module>/src/migrations/`, a matching test under `modules/<module>/tests/migrations/`, refreshes the `migrations.ts` barrel export, and creates the module's `bin/migration/up.ts` and `bin/migration/down.ts` runners if they are missing.

```bash theme={null}
# Generate a migration in the shared module
talos migration:create

# Target a specific module
talos migration:create --module=auth
```

| Option     | Description                                    | Default  |
| ---------- | ---------------------------------------------- | -------- |
| `--module` | Target module the migration is generated into. | `shared` |

The migration file name is derived from a timestamp version automatically, so there is no `--name` option. The generated class is a ready-to-fill stub:

```typescript theme={null}
import { decorator, type IMigration, type MigrationClassType } from "@talosjs/migrations";
import type { TransactionSQL } from "bun";

@decorator.migration()
export class Migration20240115103045 implements IMigration {
  public async up(tx: TransactionSQL): Promise<void> {
    // await tx`...`;
  }

  public async down(tx: TransactionSQL): Promise<void> {
    // await tx`...`;
  }

  public getVersion(): string {
    return "20240115103045";
  }

  public getDependencies(): MigrationClassType[] {
    return [];
  }
}
```

See the [`migration:create` command](/cli/commands/migration-create) for the full reference.

## Implement `up()` and `down()`

Fill in `up()` with the schema change and `down()` with its exact reverse. Both receive the transaction (`tx`) the runner wraps around the migration and the pooled `sql` client:

```typescript theme={null}
import { decorator } from "@talosjs/migrations";
import type { IMigration, MigrationClassType } from "@talosjs/migrations";
import type { SQL, TransactionSQL } from "bun";

@decorator.migration()
export class Migration20240115103045 implements IMigration {
  public async up(tx: TransactionSQL, sql: SQL): Promise<void> {
    await tx`CREATE TABLE users (
      id SERIAL PRIMARY KEY,
      email VARCHAR(255) NOT NULL UNIQUE,
      created_at TIMESTAMP DEFAULT NOW()
    )`;
  }

  public async down(tx: TransactionSQL, sql: SQL): Promise<void> {
    await tx`DROP TABLE IF EXISTS users`;
  }

  public getVersion(): string {
    return "20240115103045";
  }

  public getDependencies(): MigrationClassType[] {
    return [];
  }
}
```

## Order with dependencies

The runner sorts migrations by version, but a migration's dependencies always run first regardless of timestamp. Declare them in `getDependencies()` when one change must precede another:

```typescript theme={null}
public getDependencies(): MigrationClassType[] {
  return [Migration20240115103045];
}
```

Before running a migration, the runner resolves each dependency from the container and runs it first, recursively.

## Migrations that hold up

Make `down()` the exact reverse of `up()`: drop precisely what `up` adds, since an asymmetric or irreversible `down()` is a bug. Once a migration has run on a shared database, never edit it — add a new corrective migration instead of rewriting one that already ran.

Add indexes for foreign keys, `WHERE` and `ORDER BY` columns, and unique constraints in `up()`, then drop them in `down()` before the table or column they cover. Keep column types, nullability, and lengths in sync with the entity definition, so a non-nullable column is also `NOT NULL` in the migration.

## Next steps

* [Running migrations](/migrations/running): apply pending migrations with `talos migration:up`.
* [Rolling back](/migrations/rollback): reverse a migration with `talos migration:down`.
