Skip to content
Using Rent

Migrations

Your application already has posts. Now the post editor needs a summary field. Add it to RSL, then let Rent write the SQL that brings the database up to date:

model Post {
  id      BigInt  @id
  title   String
  summary String? // New: older posts can leave this empty.
}

Keep DATABASE_URL in the project-root .env. During local development, run:

rent migrate dev --name add_post_summary
rent generate

The first command writes and applies a migration, then checks that the database matches your schema. The second updates the Rust client. Review the generated SQL and commit the migration files with your RSL change.

Drag to pan · scroll or pinch to zoom · use the controls to reset or open fullscreen

Review a change before applying it

For a separate review step, inspect the proposed change and write it to a file:

rent migrate preflight
rent migrate diff
rent migrate plan --name add_post_summary

In deployment, apply the files you reviewed instead of calculating a new change:

rent migrate validate
rent migrate apply
rent migrate status

preflight, diff, and plan read the same RSL source as rent generate: rent/schema.rsl. Use --to only when a workspace keeps its RSL schema at another path.

Use migration credentials

Use a dedicated migration database role with access to the complete schema. PostgreSQL can hide individual columns, triggers, and trigger conditions from restricted application roles. Rent rejects incomplete inspection with an actionable error before planning changes. An invisible object is not treated as an absent object. Table ownership also grants visibility of trigger conditions. Keep the application's runtime role restricted. Use migration credentials only for deployment and schema administration.

Keep a reviewable history

New migration files use 14-digit UTC timestamp prefixes such as 20260910022456_create_users.sql. Rent applies them lexically and rejects a pending migration that sorts before an already-applied version.

diff supports SQL and semantic JSON output. plan writes append-only SQL and refreshes rent.sum. It holds only what your migration files do not yet do, so several changes can be planned before any is applied. validate proves that committed files still match the manifest. apply executes those exact files in version order. It never recalculates the reviewed migration. It uses backend locks and transactions, writes a dirty journal record before execution, and records each file checksum after success.

Plan from migration history

Migration files describe what a fresh database will become. rent migrate plan compares that result with the current RSL schema, so planning several changes before applying them does not repeat earlier SQL:

rent migrate plan --name add_post_title

# Add the next field to the RSL schema.
rent migrate plan --name add_post_summary

rent migrate validate
rent migrate apply
rent migrate drift

The second file adds only the summary. The pending first file already accounts for the title. On server databases, Rent reconstructs the migration history in a temporary shadow database beside the development database, then removes it. If the migration role cannot create databases, set SHADOW_DATABASE_URL to a dedicated disposable database that Rent may reset. Keep it separate from application and production databases. See rent migrate plan for the command options.

Adopt an existing database

A database created before Rent managed it has tables no migration made. Baseline it once: describe it, write the SQL that creates it as the first migration, and record that migration as applied without running it.

rent db pull
rent migrate diff --from-empty > rent/migrations/20260910000000_baseline.sql
rent migrate seal
rent migrate resolve --applied 20260910000000

From then on rent migrate dev and rent migrate plan record only new changes, and a fresh database built with rent migrate apply gets the baseline too. Run resolve against every existing database, such as staging and production, before its first apply.

Baselining and shadow planning work together: the baseline reconstructs the existing schema when migration history is replayed, while resolve --applied keeps those creation statements from running against databases that already contain their tables. Keep the baseline SQL in version control with rent.sum.

Review removals and type changes

Destructive changes require both diff participation and explicit application approval:

rent migrate plan --name remove_legacy_sessions --drop-objects

rent migrate apply --allow-destructive

Approval covers the migrations a command writes or runs: once a destructive migration is applied, later safe migrations seal and apply without it.

Changing a model's @id or @@id replaces its primary key. The plan drops the foreign keys that reference the old key, replaces the key, and adds them back, so a key widened with tenant_id keeps its rows and its references. On SQLite the table is rebuilt. Replacing a key needs review because existing rows may not be unique under the new one. Removing a key outright is destructive.

Changing a column's type converts the rows it holds. A text or enum column that becomes Json keeps each value as a JSON string, so 'swiss' becomes "swiss": on PostgreSQL through USING to_jsonb(...), on MySQL and MariaDB by quoting the values in place before the column changes type. Text holding JSON documents stays text inside JSON. Parse it with a hand-written migration if that is what you want. SQLite stores both as text, so it plans no change and leaves the rows as they were.

When a migration changes the physical type of a foreign-key column or its referenced column, Rent releases that constraint before altering the endpoints and restores its final declared definition afterwards. This also applies to compound foreign keys. The stored values must still be convertible to the new type. On PostgreSQL, a failed cast rolls the migration back, including the constraint changes. Review destructive migration approval separately from whether existing values can be cast successfully.

Check the result and recover deliberately

Use rent migrate drift after deployment to verify that the live database still matches the RSL schema. Use rent migrate history to inspect the durable journal. rent migrate resolve records a verified failed migration as applied or rolled back. It never runs automatically.

During local development, rent migrate dev combines plan, apply, and a final fixed-point inspection. It reports "Database matches the RSL schema" only when nothing was withheld. Otherwise it lists the live objects the schema no longer declares and how to remove them. rent migrate preflight is the read-only safety review before that step. rent migrate reset --yes rebuilds a disposable development database entirely from migration history.

PostgreSQL may display extra casts or parentheses when you inspect a CHECK or a column default. Keep the readable expression in RSL. Equivalent literal spellings do not need another migration. Changing a bound, enum label, pattern or default value still produces a schema change.

Author a data migration

Schema diffs cannot decide how existing application data should be transformed. Create an editable migration for a backfill or cleanup:

rent migrate create --name backfill_user_slugs

Edit the generated SQL file, review it, then seal its checksum:

rent migrate seal
rent migrate validate
rent migrate apply

seal validates every edited draft before changing rent.sum. Destructive SQL is rejected unless the reviewed command includes --allow-destructive. The migration lifecycle tutorial executes an expand → backfill → contract release from beginning to end.

Extensions already installed by the database platform

Some PostgreSQL images and managed services preinstall extensions, sometimes through template1 so that shadow and new databases inherit them. Declare extensions that belong to the target environment in the RSL schema, including the installed schema and a supported version where required. Rent compares the declared database catalog. It does not silently ignore undeclared extensions. An undeclared extension may therefore appear as a proposed removal or cause a shadow/live catalog mismatch.

rent migrate dev and rent migrate plan record declared extensions that migration history has not installed, even when the shadow database inherits them from template1. The migration uses CREATE EXTENSION IF NOT EXISTS before dependent tables, so the same history works on a fresh database. Later migrations do not repeat that installation. Declared version upgrades and schema relocations still produce their normal ALTER EXTENSION operations. Preinstallation does not bypass those checks. The destination server must provide the extension and grant the migration role permission to install it.

When an extension uses a separate namespace, include that namespace in the migration command's inspection scope, for example --schemas public,extensions. Without --schemas, PostgreSQL inspection uses the connection's current schema.

Rent does not rewrite existing migration files. If an older history omitted an extension required by an earlier migration, correct that history before deploying it to an empty database: revise an unapplied draft or use the documented baseline workflow for an already-applied history. Adding an installation at the end cannot repair an earlier dependency-ordering mistake.

Do not drop provider-owned extensions or change a shared template database just to clear a drift report. First inspect the live and shadow catalogs, choose whether the extension belongs in the target schema, and confirm the provider's extension policy. Use a disposable database for development experiments. See the TimescaleDB extension guide for its RSL declaration.