Migration CLI

Generate, run, and inspect database migrations.

The migration system tracks changes to your entity schema and generates SQL migrations.

Commands

Generate a migration


          cms migration generate
        

Diffs the current code-defined schema against the latest snapshot and generates a new migration with SQL and a schema snapshot.


          migrations/
  0001_initial/
    migration.sql
    schema.json
  0002_add_excerpt/
    migration.sql
    schema.json
        

Run pending migrations


          cms migration run
        

Applies all pending migrations in order. Each migration runs in a transaction. Applied migrations are tracked in a _migrations table.

Check migration status


          cms migration status
        

Lists all migrations with their status (applied or pending).

How it works

  1. Every migration has a .sql file and a schema.json snapshot
  2. generate diffs the current schema against the latest snapshot
  3. Changes are classified by storage type: column (DDL), JSONB (data transform), or relation (FK/junction)
  4. You review the generated SQL and commit it

Migration operations

The differ emits typed operations:

Column operations

  • add_columnALTER TABLE ... ADD COLUMN
  • drop_columnALTER TABLE ... DROP COLUMN
  • alter_columnALTER TABLE ... ALTER COLUMN

JSONB operations

JSONB fields don’t have DDL migrations. The system generates UPDATE statements that transform data in place:


          -- Adding a field with default
UPDATE posts SET body = jsonb_set(body, '{subtitle}', '"default"')
WHERE NOT body @? '$.subtitle';

-- Removing a field
UPDATE posts SET body = body #- '{old_field}';

-- Renaming a field
UPDATE posts SET body = jsonb_set(
  body #- '{oldName}',
  '{newName}',
  body #> '{oldName}'
)
WHERE body #> '{oldName}' IS NOT NULL;
        

Relation operations

  • Add single relationALTER TABLE ... ADD COLUMN x_id uuid REFERENCES ...
  • Add multiple relationCREATE TABLE entity_field (sourceId, targetId, position)
  • Remove → reverse of the above

Rename detection

The system can’t reliably detect renames (vs. add + remove). When ambiguous, it asks:


          Is "body.old_field" → "body.subtitle" a rename? (y/n)
        

Schema snapshots

Each field is serialized with its resolved base type and storage info:


          {
  name: string;
  type: string;           // original type (e.g. "color-picker")
  base: string;           // resolved base type (e.g. "text")
  storage: "column" | "jsonb" | "relation";
  columnType?: string;    // e.g. "varchar", "numeric"
  unique?: boolean;
  required?: boolean;
  to?: string | string[];
  multiple?: boolean;
  shape?: Record<string, unknown>;  // nested structure for JSONB
}
        

Previous

Config Reference