# Upgrade migrations

For developers

New installations get their data from the installer. Existing installations are brought up to date by *upgrade migrations*: small classes that run once, at startup, when a store starts a newer GrandNode version against an older database. Whenever a change adds localization resources, permissions, admin menu entries, scheduled tasks, settings or indexes, it needs both — seed data for new installs and a migration for existing ones.

### How migrations run

1. At startup the `Grand.Module.Migration` module (enabled by `FeatureManagement:Grand.Module.Migration`) reads the version stamp stored in the database.
2. It collects every class implementing `IMigration` whose `Version` is **greater** than that stamp, ordered by version and then by `Priority`.
3. It skips migrations whose `Identity` is already recorded in the migrations collection, runs the others and records each success.
4. The last migration of every version is the version stamp itself (`MigrationUpgradeDbVersion_24` for 2.4), which moves the database to that version.
5. If a migration returns `false`, the process stops before the stamp, logs the error and retries on the next start.

The consequence: a migration placed in the folder of the version a database is *already* stamped with never runs there. Migrations reach installations that upgrade from an older version.

### The shape of a migration

Migrations live in `src/Modules/Grand.Module.Migration/Migrations/<major.minor>/`. The folder `2.4` maps to the namespace segment `_2._4`.

```
using Grand.Infrastructure.Migrations;

namespace Grand.Module.Migration.Migrations._2._4;

public class MigrationUpdateResourceString : IMigration
{
    public int Priority => 0;
    public DbVersion Version => new(2, 4);
    public Guid Identity => new("6C0F2E4B-9A17-4D65-B3C8-51E0A9D77B42");
    public string Name => "Update resource string for english language 2.4";

    public bool UpgradeProcess(IServiceProvider serviceProvider)
    {
        return serviceProvider.ImportLanguageResourcesFromXml("App_Data/Resources/Upgrade/en_240.xml");
    }
}
```

| Member | Rule |
|---|---|
| `Version` | The release the change ships in. It must match the folder. |
| `Identity` | A freshly generated GUID. The runner uses it to know the migration already ran; a GUID copied from another migration makes one of them silently never run. |
| `Priority` | Order inside the version. Resources, permissions and menu migrations conventionally use `0`; migrations that depend on them run higher. |
| `Name` | Human-readable, including the version. It appears in the log. |

### Writing UpgradeProcess

Resolve what you need from the `serviceProvider`. There is no HTTP request, so there is no current store, customer or language — pass store ids explicitly.

```
public bool UpgradeProcess(IServiceProvider serviceProvider)
{
    try
    {
        var repository = serviceProvider.GetRequiredService<IRepository<ScheduleTask>>();
        if (repository.Table.Any(x => x.ScheduleTaskName == "Example task"))
            return true;   // already there: running twice must be a no-op

        repository.Insert(new ScheduleTask {
            ScheduleTaskName = "Example task",
            Enabled = false,
            TimeInterval = 60
        });
        return true;
    }
    catch (Exception ex)
    {
        serviceProvider.GetRequiredService<ILogger<MigrationExample>>()
            .LogError(ex, "Example migration failed");
        return false;
    }
}
```

- **Never throw.** Catch, log and return `false`.
- **Be idempotent.** Check before inserting; a migration can run again after a partial upgrade.
- **Do not destroy operator data.** Change only values an operator cannot have customised.
- **Preserve behaviour.** A new setting gets the default that keeps an upgraded store working as before.

### Localization resources

Do not write resource strings in C#. Add each new key twice: to `App_Data/Resources/DefaultLanguage.xml` for new installations and to the upgrade file of the release, for example `App_Data/Resources/Upgrade/en_240.xml`, which the version's `MigrationUpdateResourceString` imports. `DefaultLanguage.xml` is saved as UTF-16, so plain `grep` finds nothing in it; convert it first or use an editor.

### Other kinds of migration

Copy the file of the same name from the newest version folder rather than inventing a shape:

| Change | Follow |
|---|---|
| Permissions | `MigrationSystemPermission.cs`, `MigrationUpdateStandardPermissionNames.cs` |
| Admin navigation | `MigrationUpdateAdminSiteMap.cs` |
| Scheduled tasks | `MigrationScheduleTasks.cs` |
| Settings and store data | `MigrationUpdateMediaSettings.cs`, `MigrationStoreSingleUrl.cs` |
| Indexes | `MigrationUniqueOrderNumberIndex.cs` |
| A new version folder | Needs its own `MigrationUpgradeDbVersion_XX` deriving from `MigrationUpgradeDbVersionBase`. |

The runner also picks up `IMigration` classes from installed plugins, but they are gated by the same GrandNode database version, so they fire only when the store upgrades GrandNode itself. A plugin that changes its own data between plugin versions should handle that in its own code.

### Testing a migration locally

1. Back up the development database.
2. In the `GrandNodeVersion` collection, set `InstalledVersion` and `DataBaseVersion` back one minor version (for example from `2.4` to `2.3`).
3. Restart the application. Migrations already recorded are skipped by `Identity`, so only new ones run; the log shows each one.
4. Check the result, then make sure the version stamp is back at the current version.

### Related

- [Architecture overview](https://grandnode.com/developers-architecture-overview)
- [The AI Agent Kit](https://grandnode.com/developers-ai-agent-kit) — `.ai/templates/migration.md` and `.ai/prompts/add-migration.md`.

- [Web page](https://grandnode.com/developers-upgrade-migrations)
