SpacetimeDB Versioning and Migration Strategies: A Complete Technical Guide

SpacetimeDB handles schema evolution through module versioning, offering automatic migrations for additive changes via the ponder_migrate planner and incremental migration patterns for complex breaking changes.

SpacetimeDB versioning and migration strategies center on the concept of module versioning, where each republish compares the new ModuleDef against the stored definition to generate a migration plan. According to the clockworklabs/SpacetimeDB source code, the runtime in crates/schema/src/auto_migrate.rs implements two complementary approaches: automatic migrations for safe schema changes and incremental migrations for complex transformations.

Understanding SpacetimeDB Module Versioning

When you invoke spacetime publish, the CLI loads your module's ModuleDef and retrieves the existing definition from the database. The runtime compares these definitions to determine what has changed. This comparison happens in the migration planner, which produces a MigratePlan enum that can be either Manual or Auto.

The migration process preserves existing client connections during automatic updates, ensuring that active subscriptions remain functional. Only reducers that disappear or change signatures may trigger runtime errors for stale clients.

Automatic Migrations for Schema Evolution

Automatic migrations handle simple, additive schema changes that the engine can apply without data loss. The planner function ponder_migrate in crates/schema/src/auto_migrate.rs builds an AutoMigratePlan that executes during the publish operation.

How Automatic Migrations Work

The automatic migration flow follows four stages:

  1. Definition comparison – The runtime diffs the new ModuleDef against the stored version.
  2. Plan generationponder_migrate analyses differences and produces a MigratePlan.
  3. Policy enforcement – A MigrationPolicy (compatible or break-clients) validates the plan via MigrationPolicy::permits_plan and may require a MigrationToken for breaking changes.
  4. Execution – The runtime applies safe schema modifications while keeping active subscriptions alive.

Allowed Changes in Automatic Migrations

The SpacetimeDB automatic migration system categorizes schema changes into three groups:

Always allowed (non-breaking):

  • New tables
  • New indexes
  • Adding or removing Auto Inc annotations
  • Toggling table visibility
  • New reducers
  • Removing Unique constraints

Potentially breaking (requires migration token):

  • Adding a new column with a default value
  • Changing or removing existing reducers
  • Making a public table private
  • Removing primary-key annotations
  • Removing indexes used by join subscriptions

Forbidden (must use incremental migration):

  • Dropping tables
  • Removing or modifying existing columns
  • Adding columns without defaults
  • Inserting columns in the middle of a table
  • Adding Unique or Primary Key constraints to existing columns
  • Changing a table's scheduling flag

Handling Breaking Changes with Migration Tokens

When your schema change falls into the "potentially breaking" category, the CLI requires a migration token to proceed. Generate the token using the database identity and module hashes:

spacetime publish mydb \
  --migration-token "$(spacetime token generate --db mydb --old-hash <old> --new-hash <new>)"

The token's hash is computed from the database identity and module hashes as implemented in crates/schema/src/auto_migrate.rs (lines 159-167). This satisfies the MigrationPolicy::BreakClients path, allowing the publish to proceed while acknowledging that existing clients may experience runtime errors if they depend on changed reducers.

Incremental Migration Strategy for Complex Changes

For schema changes that violate automatic migration rules—such as dropping tables, removing columns, or changing column types—SpacetimeDB provides the incremental migration pattern. This approach avoids downtime and maintains backward compatibility during the transition.

The Four-Step Incremental Pattern

The incremental migration strategy follows a systematic approach:

  1. Introduce a new table – Create a table with the target schema (e.g., character_v2) alongside the existing table.
  2. Dual-write – Update reducers to write to both the old and new tables, ensuring legacy data stays synchronized.
  3. Lazy migration – When clients access data, check the new table first. If the record is missing, read from the old table, convert the data, insert it into the new table, and optionally update the old table.
  4. Deprecation – Once all clients have upgraded and data is fully migrated, remove the legacy tables and redundant code.

This pattern leverages SpacetimeDB's hot-swap capability to roll out schema changes without interrupting active connections.

Incremental Migration Code Example

Consider migrating a Character table to add an alliance field that cannot be automatically migrated:

// Legacy table remains for backward compatibility
#[spacetimedb::table(name = character, public)]
pub struct Character {
    #[primary_key] player_id: Identity,
    nickname: String,
    level: u32,
}

// New table with desired schema
#[spacetimedb::table(name = character_v2, public)]
pub struct CharacterV2 {
    #[primary_key] player_id: Identity,
    nickname: String,
    level: u32,
    alliance: Alliance,
}

#[derive(SpacetimeType, Clone, Copy)]
pub enum Alliance { Good, Neutral, Evil }

// Lazy migration helper
fn find_character(ctx: &ReducerContext) -> CharacterV2 {
    if let Some(c) = ctx.db.character_v2().player_id().find(ctx.sender) {
        return c; // Already migrated
    }
    let old = ctx.db.character().player_id().find(ctx.sender).unwrap();
    ctx.db.character_v2().insert(CharacterV2 {
        player_id: old.player_id,
        nickname: old.nickname,
        level: old.level,
        alliance: Alliance::Neutral,
    })
}

All reducers (create_character, rename_character, level_up_character, etc.) should be rewritten to operate on CharacterV2 while maintaining dual-write logic to the legacy character table for backward compatibility.

Migration Workflow Best Practices

When managing SpacetimeDB versioning and migration strategies in production environments, follow these guidelines:

  • Validate before deploying – Always run spacetime publish --dry-run to preview migration plans and catch forbidden changes early.
  • Use automatic migrations for additive changes – New tables, indexes, and columns with defaults should leverage the automatic path to minimize code complexity.
  • Reserve incremental migrations for structural changes – When dropping columns, changing types, or removing tables, use the dual-write pattern to maintain availability.
  • Generate tokens for breaking changes – When removing reducers or changing public table visibility, obtain a migration token to satisfy the MigrationPolicy::BreakClients requirement.
  • Monitor client compatibility – Track which client versions are active before removing legacy tables or reducers to ensure all users have migrated.

Summary

SpacetimeDB versioning and migration strategies provide two complementary approaches for evolving your database schema:

  • Automatic migrations handle additive changes through the ponder_migrate planner in crates/schema/src/auto_migrate.rs, allowing new tables, indexes, and columns with defaults without downtime.
  • Incremental migrations support complex structural changes via dual-write patterns and lazy data migration, enabling you to add non-default columns, drop tables, or modify types while maintaining backward compatibility.
  • Migration tokens authorize potentially breaking changes like reducer removal or table visibility changes through the MigrationPolicy system.

Frequently Asked Questions

What types of schema changes can SpacetimeDB handle automatically?

SpacetimeDB automatically migrates additive changes including new tables, new indexes, adding or removing Auto Inc annotations, toggling table visibility, adding new reducers, and removing Unique constraints. You can also add columns with default values, though this requires a migration token as it may break existing clients. All automatic migrations execute through the ponder_migrate function in crates/schema/src/auto_migrate.rs without interrupting active connections.

How do I perform a breaking change in SpacetimeDB without losing data?

For breaking changes that violate automatic migration rules—such as dropping tables, removing columns, or changing column types—use the incremental migration pattern. Create a new table with your updated schema, implement dual-write logic in your reducers to populate both tables, and use lazy migration to convert data when clients access it. Once all clients have upgraded and data is migrated, remove the legacy table. This approach leverages SpacetimeDB's hot-swap capability to maintain availability throughout the transition.

What is a migration token and when do I need one?

A migration token is a cryptographic hash that authorizes potentially breaking schema changes in SpacetimeDB. You need one when making changes classified as "potentially breaking" by the MigrationPolicy system, such as adding columns with defaults, changing or removing reducers, making public tables private, or removing primary-key annotations. Generate the token using spacetime token generate with your database identity and module hashes, then pass it to spacetime publish with the --migration-token flag to satisfy the MigrationPolicy::BreakClients path.

Can I rename a column in SpacetimeDB using automatic migration?

No, renaming a column is not supported by SpacetimeDB's automatic migration system. The migration planner in crates/schema/src/auto_migrate.rs treats column removal or modification as forbidden changes that would result in data loss. To rename a column, you must use the incremental migration pattern: create a new table with the updated schema including the renamed column, implement dual-write logic to populate both tables, migrate existing data to the new table, and eventually remove the old table once all clients have upgraded.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →