A critical vulnerability was discovered in React Server Components (Next.js). Our systems remain protected but we advise to update packages to newest version. Learn More

Robert Svallin
Jan 23, 2026
  116
(6 votes)

From 12 to 13 preview: A Developer's Guide to testing an Optimizely CMS 13 Alloy Site

The release of Optimizely CMS 13 marks a significant step forward, embracing a more composable and headless-first architecture. While this unlocks powerful new capabilities, it also introduces important changes to the underlying framework.

This guide provides a hands-on walkthrough for upgrading a standard CMS 12 Alloy starter site to the CMS 13 preview. We'll cover dependency updates, code migrations for obsolete APIs, and the new application configuration model.

Step 1: Create a Baseline CMS 12 Site

First, let's establish a starting point. Create a fresh CMS 12 Alloy site using the Optimizely templates. This ensures we have a clean, working installation before we begin the upgrade.

# Create a new Alloy project
dotnet new epi-alloy-mvc -n alloy13preview

# Build and run the site
dotnet build
dotnet run
 

Once the site is running, register a new user account so you can log in. Verify that the site is fully functional. When you're ready, shut down the application.

Step 2: Update Project Dependencies

With our baseline established, the next step is to update the project file and NuGet packages.

  1. Update the Target Framework: Open the .csproj file and change the target framework to

    <PropertyGroup>
      <TargetFramework>net10.0</TargetFramework>
    </PropertyGroup>
  2. Update NuGet Packages: Update all EpiServer.* package versions to 13.0.0-preview2.

  3. Add AspNetIdentity: CMS 13 decouples the UI's identity management. Add a new package reference for EPiServer.CMS.UI.AspNetIdentity. 

    <PackageReference Include="EPiServer.CMS.UI.AspNetIdentity" Version="13.0.0-preview2" />

Step 3: Code Migration and Mitigating Obsolete APIs

CMS 13 refactors several core APIs. The compiler will now report a series of warnings and errors related to obsolete members. Let's work through them.

A Friendly Pro-Tip: As you begin the migration, make it a habit to check the compiler warnings in your IDE or build output. These warnings are your roadmap to finding every instance of a deprecated or obsolete API in your specific project. While this guide covers the common changes for the Alloy template, your codebase may have other areas needing attention.

Use ContentReference and ContentLink

The PageReference and PageData.PageLink types are now obsolete. This change reflects a broader shift to treat all content more generically. The fix is a straightforward replacement across your solution:

  • Replace PageReference with ContentReference.

  • Replace .PageLink with .ContentLink.

Replace SiteDefinition with the New Application Model

The concept of SiteDefinition is replaced by a more flexible Application Model. An application connects a starting point in the content tree with a specific rendering mode (e.g., "In-Process" or "Headless") and hostnames.

To resolve this, you'll need to inject IApplicationResolver into your controllers and services to get the context of the current application (IApplication).

Here is an example of how to refactor the StartPageController:

using alloy13preview.Models.Pages;
using alloy13preview.Models.ViewModels;
using EPiServer.Web;
using EPiServer.Web.Mvc;
using Microsoft.AspNetCore.Mvc;
using EPiServer.Applications;
using EPiServer.Shell.Security;
using EPiServer.Web.Routing;

namespace alloy13preview.Controllers;

public class StartPageController : PageControllerBase<StartPage>
{
  private readonly IApplicationResolver _applicationResolver;
  
  public StartPageController(IApplicationResolver applicationResolver)
  {
    _applicationResolver = applicationResolver;
  }

  public async Task<IActionResult> Index(StartPage currentPage, CancellationToken cancellationToken)
  {
    var model = PageViewModel.Create(currentPage);
    var application = await _applicationResolver.GetByContextAsync(cancellationToken);

    var website = application as Website;
    
    if (website is not null && website.RoutingEntryPoint.CompareToIgnoreWorkID(currentPage.ContentLink))
    {
      // Connect the view models logotype property to the start page's to make it editable
      var editHints = ViewData.GetEditHints<PageViewModel<StartPage>, StartPage>();
      editHints.AddConnection(m => m.Layout.Logotype, p => p.SiteLogotype);
      editHints.AddConnection(m => m.Layout.ProductPages, p => p.ProductPageLinks);
      editHints.AddConnection(m => m.Layout.CompanyInformationPages, p => p.CompanyInformationPageLinks);
      editHints.AddConnection(m => m.Layout.NewsPages, p => p.NewsPageLinks);
      editHints.AddConnection(m => m.Layout.CustomerZonePages, p => p.CustomerZonePageLinks);
    }

    return View(model);
  }
}

You will need to apply a similar pattern to other places in the code that reference SiteDefinition.Current. For SiteDefinition.Current.RootPage, you can replace it with ContentReference.RootPage.

Modernize Dependency Injection

Service location using InitializationEngined.Locate is obsolete. Instead, use constructor injection to get an IServiceProvider instance.

  • Before: context.Locate.Advanced.GetInstance<T>()

  • After: Inject IServiceProvider and call serviceProvider.GetRequiredService<T>() In an IInitializationModule, you can access it via context.Services.

Other Small API Changes

  • IContentTypeRepository<T>: The generic argument is no longer needed. Change IContentTypeRepository<PageType>() to IContentTypeRepository()
  • PageController.PageContext.Page This property is now of type IContent. Update your code to reflect this change.

Step 4: Configuration Updates

Next, we need to make a few adjustments in Startup.cs and appSetting.json

  1. Enable Database Compatibility Update: In Startup.cs, add the following to allow the database compatibility level to be updated automatically.

    services.Configure<DataAccessOptions>(options =>
    {
        options.UpdateDatabaseCompatibilityLevel = true;
    });
  2. Configure Content Graph: Content Graph is enabled by default in the preview of CMS 13 and cant be disabled. This will likely change in subsequent releases. You must add your credentials to appSettings.json.

    "Optimizely": {
      "ContentGraph": {
        "GatewayAddress": "https://staging.cg.optimizely.com",
        "AllowSendingLog": "true",
        "SingleKey": "INSERT SINGLEKEY HERE",
        "AppKey": "INSERT APPKEY HERE",
        "Secret": "INSERT SECRET HERE"
      }
    }
  3. Install Visitor Groups: Due to a known issue in the preview, the menu system will not render correctly unless Visitor Groups are installed. Add this to Startup.cs

    services.AddVisitorGroups();

Step 5: Fixing the Post-Upgrade 404

After making all the changes, run the application:

dotnet run

You will notice the site returns a 404 Not Found error. This is expected. The migrated database still has the old SiteDefinition configuration, which doesn't align with the new Application Model.

Follow these steps to fix it:

  1. Navigate to the CMS admin interface: https://localhost:5000/Optimizely/CMS.

  2. Go to Settings > Applications. If the page fails to render, clear your browser cache and reload. You will see a default "Headless" application.

  3. Edit the application and click Delete Application.

  4. Click Create New Application.

    • Set the Type to In Process.

    • Select your original Alloy start page as the Application start page.

    • Give it a name.

  5. Edit the new "In Process" application you just created.

    • In the Hosts section, add a new host with the name localhost:5000 and set it as the default.

Your Alloy site should now render correctly on the frontend, and preview in Edit mode will be functional.

Conclusion

Congratulations! You have successfully upgraded your Alloy site to CMS 13. This process highlights the key architectural shifts in the new version, particularly the move to a composable Application Model and modernized APIs. You are now ready to explore the new features and possibilities of Optimizely CMS 13.

Important Note: The steps outlined in this guide are intended for developers to explore the CMS 13 preview with the Alloy template. This is an incomplete preview, and these instructions are not recommended for use with an actual development project and certainly not a live production site. We will share more as we get closer to the release date of CMS 13.

Jan 23, 2026

Comments

Graham Carr
Graham Carr Jan 23, 2026 03:08 PM

I am getting the following issue when trying to upgrade the Alloy site to CMS 13

'IUrlHelper' does not contain a definition for 'ContentLinkTarget' and no accessible extension method 'ContentLinkTarget' accepting a first argument of type 'IUrlHelper' could be found (are you missing a using directive or an assembly reference?)

what do I need to change or reference to get round this issue??

Michał Mitas
Michał Mitas Jan 23, 2026 03:28 PM

For those, who like me may have missed Opti templates, you can install them with: 

dotnet new -i EPiServer.Templates



Robert Svallin
Robert Svallin Jan 23, 2026 03:54 PM

Graham, I would suggest discussing the CMS 13 preview on Slack in the channels.

Please login to comment.
Latest blogs
A day in the life of an Optimizely OMVP: Opti North Manchester - January 2026 Meetup Recap

There's something special about the Optimizely community in the North. On 22nd January, we gathered at the Everyman Cinema in Manchester for the...

Graham Carr | Jan 23, 2026

Beyond the widget - making Optimizely Content Recommendations work for you

Optimizely recommendation data you can render your way. A frontend-focused guide to bypassing the widget, with practical query filtering advise and...

Tom Robinson | Jan 22, 2026 |

Announcing the Jhoose Commerce API for Optimizely Commerce Connect

A layered framework for Optimizely Commerce Connect that accelerates delivery, supports headless architecture, and simplifies carts, checkout, and...

Andrew Markham | Jan 22, 2026 |