---
url: https://docs.tailor.tech/erp-kit/operate/migration.md
description: >-
  Migrate the data in a deployed ERP Kit app when a release changes module
  tables in a breaking way.
---

# Migrate Data on Upgrade

Module tables are part of your app's database. When an ERP Kit release renames, removes, or tightens a field or table in a module your app uses, deploying the new version changes the schema, but **it does not migrate the data already stored**. ERP Kit does not ship automatic migrations or compatibility aliases. Your app migrates its own data with [TailorDB migrations](/sdk/services/tailordb-migration).

ERP Kit is at an early stage, so any release may include breaking changes to module tables. Check every upgrade with the update advisor as described below.

::: tip When you need this
You need a migration only for a workspace with data you want to keep, such as staging and production, and only when a release changes a table your app uses. A development workspace can instead be reset with `pnpm run seed:reset`. Optional additions, and changes to modules you do not use, need no migration.
:::

## Enable migrations

The app templates do not enable migrations. Enable them on the backend's TailorDB namespace before the first deploy to a workspace whose data you keep, so every later schema change is recorded. In `apps/<app>/backend/tailor.config.ts`:

```typescript
export default defineConfig({
  // ...
  db: {
    "main-db": {
      files: [`./src/modules-db.ts`],
      gqlOperations: "query",
      migration: {
        directory: "./migrations",
        // Optional. Defaults to the first machine user in auth.machineUsers.
        machineUser: "developer",
      },
    },
  },
});
```

Then create the baseline snapshot from the current tables, and commit the `migrations/` directory:

```bash
# in apps/<app>/backend
pnpm exec tailor tailordb migration generate
```

The migration machine user needs read and write access to every table the scripts touch. See [Machine user and permissions](/sdk/services/tailordb-migration#machine-user-and-permissions).

If the app is already deployed and its schema matches your local tables, the next deploy only records the baseline. See [Initial setup](/sdk/services/tailordb-migration#initial-setup) for other cases.

## Generate the migration for an upgrade

The `erp-kit-update-advisor` skill finds the database changes between your installed version and the target. When the database your app uses has breaking changes, it also generates a migration scaffold in an isolated working copy, without changing your project. Run it before you upgrade, as part of [Update packages](/erp-kit/operate/update-packages):

```
/erp-kit-update-advisor
```

The report lists the breaking changes, the generated migration number and files, the data transformations still to write, and any open questions. It marks the result as "generated; implementation and verification required". A generated scaffold alone does not migrate any data.

To generate the migration yourself instead, upgrade the packages, then run from the backend:

```bash
pnpm exec tailor tailordb migration generate --name "upgrade erp-kit to <version>"
```

Do not use `--init` on a deployed project; it discards the history.

## Write the transformation

Fill in each generated `migrate.ts`. The SDK guide covers the [script structure](/sdk/services/tailordb-migration#migration-script-anatomy), [field renames](/sdk/services/tailordb-migration#renaming-a-field), and [table renames](/sdk/services/tailordb-migration#renaming-a-table). For ERP data, also follow these rules:

* **Keep what the books depend on.** Preserve quantities, amounts, record IDs, references between records, and the history needed to reverse a posting.
* **Do not replay history through module commands.** Posting commands create inventory movements and journal entries; running them again double-posts. Transform the stored rows directly.
* **Ask when the mapping is unclear.** If a release changes what a value means and the mapping to the new value is a business decision, decide it with the business owner instead of guessing.
* **Migrate stored values too.** Renamed enum values, source-document discriminators, and permission scopes stored on users and roles need the same transformation as table and field names.
* **Finish every `TODO`.** Do not delete `TODO(tailor-migration-review)` markers or pass `--no-script` to hide unfinished work. Initial values for new required fields are not a substitute for a real transformation.

## Deploy

`pnpm run deploy` runs pending migrations automatically, in order, before the rest of the deploy. For each one, it relaxes the breaking constraints, runs `migrate.ts`, then applies the final schema.

What happens when a script fails depends on how it is written:

* **A single `main` function, or one step:** it runs in one transaction. If it throws, its data changes roll back, the schema returns to the previous migration, and the next deploy runs it again.
* **Two or more `steps`:** each step runs in its own transaction. If a step fails after an earlier step completed, the completed steps stay applied and the migration stays **in progress**. The affected tables stay in maintenance mode, with GraphQL writes and record events disabled, until a later deploy finishes the remaining steps. Write every step so it can run again on rows it has already migrated.

See [Splitting a migration into steps](/sdk/services/tailordb-migration#splitting-a-migration-into-steps) and [Failure recovery](/sdk/services/tailordb-migration#failure-recovery).

Before deploying to staging or production:

1. Pause writes to the app, or schedule the deploy for a quiet period.
2. Keep a copy of the affected data that you can restore from. A migration checkpoint is not a data backup, and there is no automatic down-migration; see [Rollback strategy](/sdk/services/tailordb-migration#rollback-strategy).
3. Deploy, then check what matters to the business against the real data, such as stock levels, open balances, and that every reference still resolves.
