# Single Block Migration for Umbraco 18

{% updates format="full" %}
{% update date="2025-10-27" tags="early-access" %}

## Early Access - Umbraco 18

This article describes a migration that will not be enabled by default until Umbraco version 18. The feature is available in Umbraco version 17 for early access only, meaning you can run it manually using your own migration plan ahead of the version 18 release.
{% endupdate %}
{% endupdates %}

### Introduction

Version 17 introduced the single block property editor. Its purpose is to replace the "single mode" option that exists in the Block List property editor. This is part of the broader effort to ensure type consistency across core property editors.

### Included migration

Umbraco ships with a migration to:

* Update all block list Data Types that have been properly configured in "single" mode.
* Update all (nested) property data that uses this Data Type.

The migration was added in Umbraco 17 but disabled. It now runs by default during the upgrade to Umbraco 18.

### Pre-running the migration

You can run the migration at any time by using your own migration plan, as shown in the example below. If you run this migration yourself, the default Umbraco migration won't update any data. It only changes data in the old format.

```csharp
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Migrations;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Migrations;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade;
using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_18_0_0;

namespace SingleBlockMigrationRunner;

public class TestMigrationComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.AddNotificationAsyncHandler<UmbracoApplicationStartingNotification, RunTestMigration>();
    }
}

public class RunTestMigration : INotificationAsyncHandler<UmbracoApplicationStartingNotification>
{
    private readonly IMigrationPlanExecutor _migrationPlanExecutor;
    private readonly ICoreScopeProvider _coreScopeProvider;
    private readonly IKeyValueService _keyValueService;
    private readonly IRuntimeState _runtimeState;

    public RunTestMigration(
        ICoreScopeProvider coreScopeProvider,
        IMigrationPlanExecutor migrationPlanExecutor,
        IKeyValueService keyValueService,
        IRuntimeState runtimeState)
    {
        _migrationPlanExecutor = migrationPlanExecutor;
        _coreScopeProvider = coreScopeProvider;
        _keyValueService = keyValueService;
        _runtimeState = runtimeState;
    }

    public async Task HandleAsync(UmbracoApplicationStartingNotification notification, CancellationToken cancellationToken)
    {
        if (_runtimeState.Level < RuntimeLevel.Run)
        {
            return;
        }

        // One-off migration plan
        var migrationPlan = new MigrationPlan("Single Block Migration");

        // define the step
        migrationPlan.From(string.Empty)
            .To<MigrateSingleBlockList>("test-run-singleBlock-migration");

        // Go and upgrade our site (Will check if it needs to do the work or not)
        // Based on the current/latest step
        var upgrader = new Upgrader(migrationPlan);
        await upgrader.ExecuteAsync(
            _migrationPlanExecutor,
            _coreScopeProvider,
            _keyValueService);
    }
}

```

### Extending the migration

If your non-core property editor nests content and stores it within its own value, you must extend the migration. To do this, create and register a class that implements `ITypedSingleBlockListProcessor` and register it. See how the built-in types are registered at `Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_18_0_0.SingleBlockList.MigrateSingleBlockListComposer`. The interface needs the following properties and methods:

* `IEnumerable<string> PropertyEditorAliases`: The alias of the property editor as defined in its DataEditor attribute. Since a processor can support multiple editors if they use the same model, it takes an IEnumerable rather than a single string. These aliases are used to limit the amount of data fetched from the database.
* `Type PropertyEditorValueType`: The type of value the property editor would return when `valueEditor.ToEditor()` is called.
* `Func<object?, Func<object?, bool>, Func<BlockListValue, object>, bool> Process` The function to run when the main processor detects a value that matches your processor. The function must support the following parameters:
  * `object?`: The value passed in from the outer caller or the top-level processor.
  * `Func<object?, bool>` The function the processor calls when it detects nested content. This is passed in from the top-level processor.
  * `Func<BlockListValue, object>` The function called when the outer layer of the current value is a block list that needs to be converted to a single block. This should only be called from the core processors. It is passed around to make recursion a little easier.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.umbraco.com/umbraco-cms/18.latest/get-started/upgrading-and-migrating/find-your-upgrade-path/single-block-migration.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
