> ## Documentation Index
> Fetch the complete documentation index at: https://configurate.crystaworld.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Schema Versioning and Automatic Configuration Migrations

> Version your configuration schema and define migration steps that automatically transform stored data when your app upgrades to a new schema version.

As your application evolves, the shape of your configuration changes: fields get added, renamed, or removed, and nested structures get reorganized. Without a migration strategy, loading an older config file with a newer schema either silently discards data or throws validation errors. tauri-plugin-configurate solves this with built-in schema versioning and ordered migration steps that run automatically on `load()` — no extra code required at call sites.

## Why migrations?

Every time you ship a new version of your app that changes the config schema — adding a required field, splitting one field into two, removing a deprecated key — any existing user config on disk is out of date. Migrations let you describe exactly how to transform version N data into version N+1 data. The plugin applies as many steps as needed in sequence, then saves the result back to disk so subsequent loads are already at the current version.

## How it works

<Steps>
  <Step title="Set a version on your Configurate instance">
    Pass `version: <number>` to the `Configurate` constructor. This is the **current** schema version your code expects. The plugin writes this number to a reserved `__configurate_version__` key inside every config it saves.
  </Step>

  <Step title="Define migration steps">
    Pass a `migrations` array of `MigrationStep` objects. Each step declares the version it upgrades **from** and an `up` function that receives data at that version and returns data at the next version.
  </Step>

  <Step title="Call load() as normal">
    On `config.load()`, the plugin reads the stored version number, compares it to the current `version`, and runs any needed migration steps in ascending order. You do not call migrations manually.
  </Step>

  <Step title="Migrated data is auto-saved">
    After running migrations, the plugin saves the updated data back to storage automatically so the next load starts at the current version without repeating the migration.
  </Step>
</Steps>

## MigrationStep interface

```typescript theme={null}
interface MigrationStep<TData> {
  version: number;                    // The version this step upgrades FROM
  up: (data: TData) => TData;         // Transform function: returns data at version+1
}
```

Each `up` function receives the full config object at `version` and must return a new object valid at `version + 1`. You are responsible for returning a new object (or a mutation of the received one) — do not rely on the plugin merging partial results.

If a required migration step is missing (for example, you have data at version 1 but no step with `version: 1`), the plugin throws an error at load time.

## Full example

The following example tracks a config through three versions of a fictional app:

* **v0 → v1:** Added a `fontSize` field that did not exist before.
* **v1 → v2:** Replaced `fontFamily` (a free-form string) with a constrained `displayMode` enum.

```typescript theme={null}
import {
  BaseDirectory,
  Configurate,
  JsonProvider,
  defineConfig,
} from "tauri-plugin-configurate-api";

// Current schema — version 2
const schema = defineConfig({
  theme: String,
  language: String,
  displayMode: String,   // replaces fontFamily from v1
});

const config = new Configurate({
  schema,
  fileName: "app.json",
  baseDir: BaseDirectory.AppConfig,
  provider: JsonProvider(),
  version: 2,
  migrations: [
    {
      // Upgrade v0 → v1: add missing fontSize field
      version: 0,
      up: (data) => ({ ...data, fontSize: 14 }),
    },
    {
      // Upgrade v1 → v2: replace fontFamily with displayMode
      version: 1,
      up: (data) => {
        const { fontFamily, ...rest } = data as Record<string, unknown>;
        return { ...rest, displayMode: "system" };
      },
    },
  ],
});

// Migrations run automatically — no special call needed here
const { data } = await config.load().run();
```

When a user who has a v0 config on disk loads the app, the plugin runs the `version: 0` step, then the `version: 1` step, saves the result at version 2, and returns the migrated data — all transparently.

## Migration rules

| Rule                                   | Detail                                                                                                        |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| Steps run in ascending `version` order | The plugin sorts steps by `version` before applying them, regardless of the order you list them in the array. |
| Each step upgrades exactly one version | A step with `version: 1` transforms v1 data into v2 data. There is no support for skipping versions.          |
| All intermediate steps must be present | If stored data is at v0 and current version is v3, steps for v0, v1, and v2 must all exist.                   |
| Migrated data is auto-saved            | After migration the updated data is written back to storage so future loads skip migration.                   |
| Already-current data is not re-saved   | If `__configurate_version__` in the stored data already equals `version`, no migration or extra write occurs. |

## The `__configurate_version__` key

<Note>
  The plugin stores the current schema version in a reserved key named `__configurate_version__` inside your config data. You will see it if you open the raw config file in a text editor. Do not modify or delete this key manually — the plugin uses it to determine whether a migration is needed and which step to start from. If the key is absent, the plugin treats the stored data as version `0`.
</Note>

You do not need to include `__configurate_version__` in your schema definition; the plugin manages it transparently.

## Handling a failed auto-save

After migrations run, the plugin attempts to write the migrated data back to disk. If that write fails (for example, because the file is read-only or disk space is full), the error is silently swallowed.

<Tip>
  A failed auto-save after migration is **non-fatal**. The migrated data is still returned to your application and is fully usable for the current session. On the next load the migration will simply run again from the stored (un-migrated) version. To surface disk errors proactively, enable `validateOnRead` in your `SchemaValidationOptions` — it will not catch write errors directly, but your monitoring can detect repeated migrations as a signal of a persistent write failure.
</Tip>
