OrchardCore Data Migration
SkillDatabases & dataLets your agent write OrchardCore data migration classes that change content types, database tables, and module schema versions.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the OrchardCore Data Migration skill
About this capability
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.
What this skill tells your AI
The instructions your AI receives, as published by orchardcms/orchardcore in .agents/skills/orchardcore-data-migration/SKILL.md and read by ahel’s review.
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
Signals
- GitHub stars
- 8k
- Forks
- 3k
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
orchardcore-data-migration- Source
- github.com/orchardcms/orchardcore