# Migrate Data

Source: https://fragment.dev/guides/migrate-data

[Documentation index](https://fragment.dev/llms.txt)

<a id="migrate-data"></a>

As your product and back-office requirements evolve you'll need to update your Schema and migrate existing data. Your Schema is immutable and versioned, and once deployed to a Ledger Fragment will enforce backwards compatibility checks. Use migrations to safely change your Schema and Ledger data while ensuring compatibility with your code, zero down-time, and data that is always immutable and auditable.

[Visual: migrations. View on the HTML page.](https://fragment.dev/guides/migrate-data)

By deploying a new Schema version, you can:

- Create new Entry Type versions
- Disable and archive old Entry type versions
- Migrate entries posted with archived Entry Type versions
- Disable and archive old Ledger Accounts
- Migrate entries posted to archived Ledger Accounts

The exact process for performing a migration depends whether you need to migrate all or some of your existing data, and whether affected balances need to stay accurate during the migration.

<a id="updating-entry-types"></a>

## Updating Entry Types

When you create a new Entry Type it will automatically be marked as V1. Once deployed, Entry Type versions are immutable and cannot be deleted from your Schema. Editing Entry types in the Dashboard will automatically create new versions as needed.

In the Schema JSON Entry Types have a `type` and `typeVersion` field that together must be unique. To ensure backwards compatibility all deployed Entry Type versions are always kept on the Schema.

```json expandable=true title="Ledger Entry with typeVersion"
{
  "key": "schema-key",
  "chartOfAccounts": {...},
  "ledgerEntries": {
    "types": [
      {
        "type": "user_funds_account",
        "typeVersion": 1,
        "lines": [
          {
            "account": { 
              "path": "assets/banks/user-cash"
            },
            "key": "funds_arrive_in_bank",
            "amount": "{{funding_amount}}"
          },
          {...}
        ]
      },
      {
        "type": "user_funds_account",
        "typeVersion": 2,
        "lines": [
          {
            "account": { 
              "path": "assets/banks/user-cash"
            },
            "key": "funds_arrive_in_bank",
            "amount": "{{funding_amount}} + {{fee}}"
          },
          {...}
        ]
      }
    ]
  }
}
```

When [posting](https://fragment.dev/markdown/guides/post-ledger-entries.md) you can specify `typeVersion` to select which version of an Entry Type to use. If not set, `1` will be used as the default.

<a id="disabling-entry-types"></a>

## Disabling Entry Types

Once you have created a new Entry Type version, you can prevent postings of the previous version by setting the old Entry Type versions's `status` field to `disabled` in your Schema.

```json expandable=true title="Schema with disabled Entry Type"
{
  "key": "schema-key",
  "chartOfAccounts": {...},
  "ledgerEntries": {
    "types": [
      {
        "type": "user_payment",
        "typeVersion": 1,
        "status": "disabled",
        "lines": [...]
      },
      {
        "type": "user_payment",
        "typeVersion": 2,
        "lines": [...]
      }
    ]
  }
}
```

When an Entry Type is disabled:

- Any attempt to post using that Entry Type will result in a `BadRequestError`
- Existing entries posted with that type will remain unchanged and queryable
- The Entry Type can later be re-enabled or archived
- You can still [reverse](https://fragment.dev/reverse-ledger-entries) or migrate entries that were posted with the disabled Entry Type

<a id="archiving-entry-types"></a>

## Archiving Entry Types

You can archive an Entry Type version to trigger the migration process for historical data. Archiving an Entry Type version:

- Requires the Entry Type version to be first be `disabled` for 45 seconds
- Hides it from the Dashboard Schema designer by default 
- Creates a `LedgerEntryDataMigration` (see below) for each Ledger using the Schema to query for its entries to migrate 
- Does not automatically migrate or change existing entries

To archive an Entry Type, set its `status` field to `archived` in your Schema:

```json expandable=true title="Schema with archived Entry Type"
{
  "key": "schema-key",
  "chartOfAccounts": {...},
  "ledgerEntries": {
    "types": [
      {
        "type": "user_payment",
        "typeVersion": 1,
        "status": "archived",
        "lines": [...]
      },
      {
        "type": "user_payment",
        "typeVersion": 2,
        "lines": [...]
      }
    ]
  }
}
```

Once archived, you can un-archive an Entry Type version by settings its `status` back to `active`. This will set the `LedgerEntryDataMigration` created for the archival to `status` `inactive`. The Entry Type version will then be available again for use in your application.

<a id="migrating-entries"></a>

## Migrating Entries

When you deploy a Schema with a newly archived Entry Type version a `LedgerEntryDataMigration` will asynchronously be created for each Ledger using the Schema. To migrate existing entries that were posted with the archived Entry Type version:

1. Enumerate `ledgerEntries` on the relevant `LedgerEntryDataMigration`. Since the Entry Type version was previously disabled, no new Ledger Entries will be added to this list.
2. Call `migrateLedgerEntry` for each Ledger Entry with the new version of the Ledger Entry. This will remove the Ledger Entry from the `LedgerEntryDataMigration`. The migration is complete when the `ledgerEntries` array is empty.

```graphql expandable=true title="LedgerEntryDataMigrations"
query GetEntryMigrations(
  $ledger: LedgerMatchInput!
  $filter: LedgerEntryDataMigrationsFilterSet
) {
  ledger(ledger: $ledger) {
    ledgerEntryDataMigrations(filter: $filter) {
      nodes {
        entryType
        typeVersion
        ledgerEntries {
          nodes {
            id
            type
            posted
            parameters
          }
          pageInfo {
            hasNextPage
            endCursor
          }
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
}
```

```json expandable=true title="LedgerEntryDataMigration variables"
{
  "ledger": { 
    "id": "ledger-id"
  },
  "filter": {
    "entryType": {
      "equalTo": "user_payment"
    }
  }
}
```

Use the `migrateLedgerEntry` mutation to migrate your Ledger Entries. This mutation's input takes an `id` of the Ledger Entry to be migrated, and the `newLedgerEntry` to migrate to. Under the hood this performs a [reversal](https://fragment.dev/markdown/guides/reverse-ledger-entries.md) of the entry to migrate followed by a repost using the new Entry.

```graphql expandable=true title="migrateLedgerEntry"
mutation MigrateLedgerEntry($input: MigrateLedgerEntryInput!) {
  migrateLedgerEntry(input: $input) {
    ... on MigrateLedgerEntryResult {
      reversedLedgerEntry {
        id
        ik
      }
      reversingLedgerEntry {
        id
        ik
      }
      newLedgerEntry {
        id
        ik
        type
        typeVersion
        posted
        created
        lines {
          nodes {
            amount
          }
        }
      }
    }
  }
}
```

```json expandable=true title="migrateLedgerEntry variables"
{
  "input": {
    "id": "entry-to-migrate-id",
    "newLedgerEntry": {
      "ledger": {
        "id": "ledger-id"
      },
      "type": "user_payment",
      "typeVersion": 2,
      "parameters": {
        "amount": "10000",
        "fee": "100"
      }
    }
  }
}
```

<a id="updating-accounts"></a>

## Updating Accounts

Like Entries, your Ledger Accounts are generally immutable and safeguarded by Fragment's backwards-compatibility rules. However a number of Account migrations are supported to ensure flexible Schema design.

<a id="updating-accounts-consistency"></a>

### Consistency

Accounts can be migrated between strongly and eventually consistent by updating the `consistencyConfig` field for the Account on the Schema. Once deployed a [`LedgerMigration`](https://fragment.dev/markdown/api-reference/ledger-types.md#core-types-ledgermigration) will be created for each of the Schema's Ledgers that will apply the update.

<a id="updating-accounts-currency"></a>

### Currency

Single-currency Accounts can be upgraded to multi-currency Accounts by updating the `currencyMode` field for the Account on the Schema. Once the Schema change is deployed a `LedgerMigration` will be created for each of the Schema's Ledgers that will apply the update.

Note:
- Multi-currency Accounts cannot be downgraded to single-currency
- You will need to ensure that you're providing the `currency` argument when reading the `balance`, `ownBalance`, or `childBalance` fields of the now multi-currency Account. No change is required if you're reading the `balances`, `ownBalances`, or `childBalances` fields of the Account.

<a id="disabling-accounts"></a>

## Disabling Accounts

When you need to prevent new entries from being posted to a Ledger Account you can disable it by setting the Account's `status` field to `disabled` in your Schema. This is useful when:

- You're migrating to a new account structure
- An account is no longer needed for new transactions
- You're preparing to archive and migrate historical data

When an Account is disabled:

- Any attempt to post Ledger Entries to it will result in a `BadRequestError`
- All Entry Type versions that post to the disabled Account must also be disabled in your Schema
- Existing entries and balances remain unchanged and queryable
- Entries can still be posted to any of its active children Accounts
- The account can later be re-enabled or archived

```json expandable=true title="Schema with disabled Account"
{
  "key": "schema-key",
  "chartOfAccounts": {
    "accounts": [
      {
        "key": "legacy-user-funds",
        "name": "Legacy User Funds",
        "template": "assets/banks/legacy-user-funds",
        "status": "disabled"
      },
      {
        "key": "user-funds",
        "name": "User Funds",
        "template": "assets/banks/user-funds"
      }
    ]
  }
}
```

<a id="archiving-accounts"></a>

## Archiving Accounts

Once an Account is disabled, you can archive it to trigger the migration process for historical data. Archiving an Account:

- Requires the Account to first be `disabled` for 45 seconds
- Hides it from the Dashboard Schema designer by default 
- Creates a `LedgerAccountDataMigration` for each Ledger using the Schema to query for its entries to migrate
- Does not automatically migrate or change existing entries posted to the Account

To archive an Account, set its `status` field to `archived` in your Schema:

```json expandable=true title="Schema with archived Account"
{
  "key": "schema-key",
  "chartOfAccounts": {
    "accounts": [
      {
        "key": "legacy-user-funds",
        "name": "Legacy User Funds",
        "template": "assets/banks/legacy-user-funds",
        "status": "archived"
      },
      {
        "key": "user-funds",
        "name": "User Funds",
        "template": "assets/banks/user-funds"
      }
    ]
  }
}
```

<a id="migrating-accounts"></a>

## Migrating Accounts

When you archive an Account, Fragment automatically creates a `LedgerAccountDataMigration` that tracks all entries posting to that account. You can query for these migrations and filter by specific `structuralPath`s:

```graphql title="Query Account migrations" expandable="true"
query GetAccountMigrations(
  $ledger: LedgerMatchInput!
  $filter: LedgerAccountDataMigrationsFilterSet
) {
  ledger(ledger: $ledger) {
    ledgerAccountDataMigrations(filter: $filter) {
      nodes {
        accountPath
        ledgerEntries {
          nodes {
            id
            type
            typeVersion
            posted
            parameters
            lines {
              nodes {
                account {
                  path
                }
                amount
              }
            }
          }
          pageInfo {
            hasNextPage
            endCursor
          }
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
}
```

```json title="Query Account migrations variables" expandable="true"
{
  "ledger": {
    "id": "ledger-id"
  },
  "filter": {
    "accountPath": {
      "equalTo": "assets/cash"
    }
  }
}
```

Before migrating a Ledger Account it is important to consider whether or not you can take downtime on reading the Account balance. This will inform whether you should run an online or offline migration.

<a id="migrating-accounts-offline-migrations"></a>

### Offline migrations

For offline migrations your Account balance will be in flux while the migration is running. To perform one:

1. Update your Schema to move to the new Account
   - Disable the old Account and create the new Account you want to migrate to.
   - Disable all Entry Type versions that post to the old Account.
   - Create new Entry Type versions that post to the new Account.
   - Deploy your Schema.
2. Update your code to move to the new Account
   - Update your code to use the new Entry Type versions.
   - Since new data is going to the new account and old data is still in the old account, both of these Account balances will be inaccurate. Ensure that any process that reads these balances is aware of this.
3. Update your Schema to remove the old Account
   - Archive your old Account and old Entry Type versions that post to it.
   - Deploy your Schema.
4. Migrate existing data
   - Query all Ledger Entries posting to the old Account using the `ledgerEntries` list on the `LedgerAccountDataMigration`.
   - Migrate your list of Ledger Entries using the new Entry Type versions. Once completed, read from the new Account balance.

<a id="migrating-accounts-online-migrations"></a>

### Online migrations

For online migrations your Account balance will stay accurate throughout the process. To perform one:

1. Update your Schema to add new Accounts and Entry Type versions
   - Create the new Account you want to migrate to. Add an additional control Account to support the migration.
   - Create new dual-write Entry Type versions that post to both the old, new, and the control Account (used to balance the Entry).
   - Create a new final Entry Type versions that post to only the new Account.
   - Deploy your Schema.
2. Start dual-writing
   - Update your code to use the new dual-write Entry Type versions for new data. Now both old and new Accounts will be updated with new data, but the new Account does not yet contain existing data, so its balance will be inaccurate.
   - Query for all Ledger Entries posting to the old Account that used the old Entry Type versions.
   - Use `migrateLedgerEntry` to migrate each of the old Ledger Entries to the new dual-write Entry Type version. Now both old and new Accounts will have matching balances.
3. Switch over to the new Account
   - Update your code to use the new Account balance.
   - Update your code to use the final Entry Type version that posts to only the new Account for new data.
4. Archive the old Account and its data
   - Disable the old Account and the dual-write Entry Type versions.
   - Deploy your Schema.
   - Archive the old account and the dual-write Entry Type versions.
   - Deploy your Schema.
   - Query all Ledger Entries posting to the old Account using the `ledgerEntries` list on the `LedgerAccountDataMigration`.
   - Use `migrateLedgerEntry` to migrate old Entries to final Entry Type versions. Once completed, the old Account will have no lines.
