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 generateThe 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.
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_summaryIn deployment, apply the files you reviewed instead of calculating a new change:
rent migrate validate
rent migrate apply
rent migrate statuspreflight, 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 driftThe 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 20260910000000From 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-destructiveApproval 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_slugsEdit the generated SQL file, review it, then seal its checksum:
rent migrate seal
rent migrate validate
rent migrate applyseal 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.
