Serverpod Migrations
Serverpod has a migration system that generates SQL for changes to models with table in .spy.yaml. The migrations are applied:
- When the
apply_migrationstool is called via theserverpodMCP. Typically during development with a runningserverpod start. - When the server is started with
dart run bin/main.dart --apply-migrationsflag. Typically when running the server in production.
When migrations are needed
- Added, removed, or renamed
tablemodels or fields in.spy.yaml. - Changed relation fields that alter generated foreign keys.
- Added, removed, or changed indexes.
Migrations are not needed when the project has no database configured (e.g. config/<runMode>.yaml with no database section).
Standard flow
The standard flow for creating and applying migrations is simplified when a serverpod start is running.
With a running serverpod start and MCP server
When the server is running from serverpod start use the serverpod MCP to:
- Create a migration using the
create_migrationtool. - Apply the migration using the
apply_migrationstool.
ALWAYS use the MCP server if it is available.
ONLY if MCP server fails to connect
When the server is not running from serverpod start use the CLI commands to:
- Ensure the code is generated by running
serverpod generate. - Create a migration using the
serverpod create-migrationcommand.
Editing a generated migration
A migration directory holds migration.sql, definition.sql and the definition.json, definition_project.json and migration.json files.
migration.sql MAY be edited by hand after it is created. Two common reasons:
- Adding a data transformation, so existing rows are migrated along with the schema.
- Turning a destructive change into a non-destructive one, by reaching the same end state through intermediate steps — for example add the new column, backfill it from the old one, then drop the old column, instead of dropping and recreating.
Never edit the other files in the directory. definition.sql is the full schema, and the *.json files are what the next serverpod create-migration diffs against, so changing them corrupts every migration created afterwards.
Two rules follow from how migrations are applied:
- A database that has no migrations installed is created from the latest
definition.sqlalone and never runsmigration.sql. An existing database applies each newermigration.sqlin order. So the schema an editedmigration.sqlends up with must stay identical todefinition.sql, and data transformations in it only affect databases that upgrade through that version. - Editing a migration that has already been applied does nothing to the databases that ran it. Create a new migration for those.
On the client side (models with database: client or database: all), the same applies to the migration SQL inside the version's migration.dart; its definition SQL and JSON files are equally off limits.
Repair migrations
If the database is in an inconsistent state, a repair migration brings it back to a consistent state. It is created by reading the live schema and diffing it against a target migration version, so the database must be reachable.
With a running serverpod start and MCP server
- Create the repair migration using the
create_repair_migrationtool. Optional arguments:version(target migration version, defaults to the latest),tag, andforce(required for destructive changes, or when no drift is detected). - Apply it using the
apply_migrationstool, which applies both pending and repair migrations without restarting the server.
ALWAYS use the MCP server if it is available.
ONLY if MCP server fails to connect
# Use the `--mode` flag to specify the run mode
# Use the `--version` flag to specify the target version
# Use the `--force` flag to create a migration with destructive changes
# Use the `--tag` flag to name the migration
serverpod create-repair-migration [--mode production] [--version <name>] [--force] [--tag <tag>]
Apply the repair migration by restarting the server with dart run bin/main.dart --apply-repair-migration. Ask the user to run this command.