name: orchardcore-data-migration description: Creates and updates OrchardCore data migrations (DataMigration classes with CreateAsync/UpdateFromX). Use when the user needs to alter content type definitions, create or change SQL index tables, patch existing content items, run a recipe migration, or version a module's schema.
OrchardCore Data Migration
This skill guides you through writing OrchardCore data migrations following project conventions.
A data migration is a class that inherits DataMigration. OrchardCore discovers its methods by reflection and runs them sequentially, per tenant, on application start. The chain starts at CreateAsync() and continues through UpdateFrom1Async(), UpdateFrom2Async(), … Each method returns the schema version number the next method must match.
When to use a migration
- Define or alter content types / parts / fields (
IContentDefinitionManager). - Create or alter SQL index tables (
SchemaBuilder). - Patch existing content item data (query +
ISession.SaveAsync). - Run a setup recipe at install/upgrade (
IRecipeMigrator). - Seed roles, settings, or other documents.
The version-chain rule (read first)
The migration runner stores the last returned number per migration class per tenant. It then calls the method whose name matches: returned 4 → runs UpdateFrom4Async(). The method's return value is the new stored version.
| Rule | Why |
|---|---|
CreateAsync runs only on a fresh install. |
Existing tenants already passed it. |
CreateAsync returns the current latest version, skipping all UpdateFromX. |
New sites get the final schema directly. |
UpdateFromXAsync runs only when stored version == X. |
Sequential upgrade path. |
| Never renumber an already-shipped method. | Other tenants store the old number; renumbering breaks their upgrade. |
Each UpdateFromX returns X+1 (usually). |
Skipping is allowed when a later step subsumes an earlier one (return a higher number to jump). |
Migration Workflow
Step 1: Create the Migrations class
Place Migrations.cs in the module root (or a Migrations/ subfolder for multiple classes, e.g. Migrations/PermissionMigrations.cs).
using OrchardCore.ContentManagement.Metadata;
using OrchardCore.Data.Migration;
namespace YourModule;
public sealed class Migrations : DataMigration
{
private readonly IContentDefinitionManager _contentDefinitionManager;
public Migrations(IContentDefinitionManager contentDefinitionManager)
{
_contentDefinitionManager = contentDefinitionManager;
}
public async Task<int> CreateAsync()
{
// ... define schema ...
// Return the latest version so new installs skip every UpdateFromX.
return 1;
}
}
SchemaBuilder is injected by the base class — do not add it as a constructor parameter.
Step 2: Register the migration
In the module Startup.cs:
services.AddDataMigration<Migrations>();
Register each migration class separately if the module has more than one.
Step 3: Write the initial CreateAsync
Return the highest version your module currently ships. Example (content part):
public async Task<int> CreateAsync()
{
await _contentDefinitionManager.AlterPartDefinitionAsync("TitlePart", builder => builder
.Attachable()
.WithDescription("Provides a Title for your content item."));
// Shortcut other migration steps on new content definition schemas.
return 2;
}
Step 4: Add UpdateFromX for each shipped change
When you change the schema in a release, append a new UpdateFromXAsync — never edit a prior one. Bump the number CreateAsync returns to match.
// This code can be removed in a later version.
public async Task<int> UpdateFrom1Async()
{
await SchemaBuilder.AlterIndexTableAsync<LinkFieldIndex>(table => table
.AddColumn<string>("BigUrl", column => column.Nullable().Unlimited()));
return 2;
}
Step 5: Test the migration
- Fresh install — run on a clean tenant; only
CreateAsyncexecutes, schema lands at the latest version. - Upgrade — start from a DB at the previous version; confirm each
UpdateFromXruns in order. - Test on every targeted provider (SQLite, SQL Server, MySQL, PostgreSQL). SQLite cannot drop columns — guard with
try/catch(seereferences/schema-builder.md).
Method signatures
| Method | Forms | Runs when |
|---|---|---|
| Create | int Create() · Task<int> CreateAsync() |
fresh install |
| Update | int UpdateFromX() · Task<int> UpdateFromXAsync() |
stored version == X |
| Uninstall | void Uninstall() · Task UninstallAsync() |
feature uninstalled |
Static or instance, sync or async — all supported. Async (...Async) is the convention for new code.
Quick Reference
Common operations
| Goal | API |
|---|---|
| Define/alter content type | _contentDefinitionManager.AlterTypeDefinitionAsync(...) |
| Define/alter part | _contentDefinitionManager.AlterPartDefinitionAsync(...) |
| Remove part / type | RemovePart(...), DeletePartDefinitionAsync(...) |
| Create SQL index table | SchemaBuilder.CreateMapIndexTableAsync<TIndex>(...) |
| Add/drop column or index | SchemaBuilder.AlterIndexTableAsync<TIndex>(...) |
| Run a recipe | _recipeMigrator.ExecuteAsync("init.recipe.json", this) |
| Patch content items | ISession.Query<ContentItem, ContentItemIndex>(...) + SaveAsync |
Type definition builder methods
| Method | Effect |
|---|---|
.Creatable() |
Appears in the New menu |
.Listable() |
Shows in content list |
.Draftable() |
Supports drafts |
.Versionable() |
Keeps version history |
.Securable() |
Per-type permissions |
.WithPart("PartName") |
Attaches a part |
Gotchas
- Always set column length on string index columns (
.WithLength(n)) or.Unlimited()— unlengthed columns fail on some providers. CreateAsyncshould return the latest version, not1, onceUpdateFromXmethods exist.- Patch loops must page by
DocumentIdand callFlushAsync()periodically (seereferences/patterns.md). - Mark obsolete
UpdateFromXmethods with// This code can be removed in a later version.but keep them until you drop support for that upgrade path.
References
references/patterns.md— content definition changes, content-item data patching, recipe migrationsreferences/schema-builder.md— index tables, columns, indexes, provider quirkssrc/docs/reference/modules/Migrations/README.md(repo) — official referenceAGENTS.md(repo root) — build commands