HomeDev GuideRecipesAPI Reference
Dev GuideAPI ReferenceUser GuideGitHubNuGetDev CommunityOptimizely AcademySubmit a ticketLog In
Dev Guide

Migrate an Alloy site from CMS 12 (Find) to CMS 13 (Graph)

Migrate an Optimizely Alloy site from Optimizely Find to Optimizely Graph when upgrading to Optimizely Content Managment System (CMS) 13 using the C# SDK

👍

Early access preview

This content is an early access preview and may change before general availability. Use the thumbs up or down at the end of this article to share feedback and help shape the final release.

This guide describes how to migrate an Optimizely Alloy site from Optimizely Find to Optimizely Graph using the Graph C# SDK.

The migration replaces the Find search client and APIs with Graph equivalents while preserving the search functionality in your site.

Migration overview

An Alloy site running on CMS 12 typically uses Optimizely Find to implement search functionality. Find is being deprecated and replaced by Optimizely Graph, which provides a GraphQL-based search platform with a C# SDK.

The migration involves the following changes:

AreaCMS 12 (Find)CMS 13 (Graph)
NuGet packagesEPiServer.Find, EPiServer.Find.CmsOptimizely.Graph.Cms.Query, Optimizely.Graph.AspNetCore
Search clientIClient / SearchClient.InstanceIGraphContentClient
NamespacesEPiServer.Find.*Optimizely.Graph.Cms.Query
ConfigurationEPiServer:FindOptimizely:Graph
Execution modelMostly synchronousAsynchronous (async / await)

Prerequisites

Before you begin the migration, ensure the following:

  • The Alloy site builds and runs on CMS 12
  • You use Git to manage source control
  • You have access to the Optimizely Graph credentials:
    • Gateway address
    • App key
    • Secret
    • Single key

Create a new branch before starting the migration.

git checkout -b feature/graph-migration

Upgrade CMS 12 to CMS 13

Upgrade the CMS platform before replacing Find with Graph. This approach isolates the platform upgrade from the search migration and simplifies troubleshooting.

Update CMS packages

Open the project .csproj file and update the CMS packages to version 13.

<!-- CMS 12 -->
<PackageReference Include="EPiServer.CMS" Version="12.*" />
<PackageReference Include="EPiServer.CMS.AspNetCore" Version="12.*" />

<!-- CMS 13 -->
<PackageReference Include="EPiServer.CMS" Version="13.*" />
<PackageReference Include="EPiServer.CMS.AspNetCore" Version="13.*" />

Update any additional EPiServer.* packages to versions compatible with CMS 13.

Restore and build the project

Run the following commands:

dotnet restore
dotnet build

Resolve any build errors before continuing.

Keep the Find packages installed at this stage. Confirm that the CMS upgrade succeeds before changing the search implementation.

Refer to the Migrate Optimizely Find to Optimizely Graph documentation for additional migration guidance.

Verify the CMS upgrade

After upgrading to CMS 13, verify that the site functions correctly before modifying the search implementation.

Run the site

dotnet run

Verification checklist

Confirm the following:

  • The site starts without errors
  • The CMS edit interface loads correctly
  • Content pages render correctly
  • Existing Find-based search still works
  • Application logs contain no errors

Run the automated tests if they exist:

dotnet test

Commit the CMS upgrade once verification succeeds.

git add -A
git commit -m "chore: upgrade CMS 12 to CMS 13"

Upgrading the CMS separately ensures that Optimizely Find issues are easier to identify and resolve.

Replace Find NuGet packages

Remove the Find packages and install the Graph packages.

Remove Find packages

Remove the following packages from the project file:

<PackageReference Include="EPiServer.Find" />
<PackageReference Include="EPiServer.Find.Cms" />
<PackageReference Include="EPiServer.Find.Framework" />

If the project contains the following package, remove it:

<PackageReference Include="Wangkanai.Detection" />

Add Graph packages

Add the Graph packages:

<PackageReference Include="Optimizely.Graph.Cms.Query" Version="3.*" />
<PackageReference Include="Optimizely.Graph.AspNetCore" Version="3.*" />

Restore packages:

dotnet restore
📘

Note

Build errors may appear at this stage because the code still references Find APIs.

Update configuration

Remove the Find configuration and add Graph configuration in appsettings.json.

Remove Find configuration

"EPiServer": {
  "Find": {
    "ServiceUrl": "https://...",
    "DefaultIndex": "yOUR-INDEX-NAME"
  }
}

Add Graph configuration

"Optimizely": {
  "Graph": {
    "GatewayAddress": "https://cg.optimizely.com",
    "AppKey": "YOUR-APP-KEY",
    "Secret": "YOUR-SECRET",
    "SingleKey": "YOUR-SINGLEKEY"
  }
}

Retrieve these values from the Graph management portal.

Update service registration

Update Startup.cs or Program.cs to remove Optimizely Find services and register Optimizely Graph services.

Remove Find services

Remove the following service registrations:

  • services.AddFind()
  • services.AddDetection()
  • app.UseDetection()

Register Graph services

Add the following registrations:

services.AddContentGraph();
services.AddGraphContentClient();
services.AddVisitorGroupsCore();

Add Graph middleware:

app.UseGraphTrackingScripts();

Update namespaces

Replace Find namespaces with Graph namespaces.

Find namespaceGraph namespace
EPiServer.FindOptimizely.Graph.Cms.Query
EPiServer.Find.ApiOptimizely.Graph.Cms.Query
EPiServer.Find.CmsOptimizely.Graph.Cms.Query
EPiServer.Find.FrameworkOptimizely.Graph.Cms.Query

Remove unused namespaces such as:

  • EPiServer.Find.Framework.Statistics
  • EPiServer.Find.Helpers.Text
  • EPiServer.Find.UI
  • Wangkanai.Detection.*

Replace the search client

Find uses IClient or SearchClient.Instance.

Graph uses IGraphContentClient, which is injected through dependency injection.

Example: Find client

private readonly IClient _client;

public SearchService(IClient client)
{
    _client = client;
}

Example: Graph client

private readonly IGraphContentClient _client;

public SearchService(IGraphContentClient client)
{
    _client = client;
}

Create a query interface

Graph queries use interfaces instead of concrete page types.

Create an interface that represents searchable content.

Example:

[ContentType]
public interface ISitePageData : IContent
{
    string MetaTitle { get; set; }
    string TeaserText { get; set; }
}

Update the base page type to implement the interface.

public abstract class SitePageData : PageData, ISitePageData
{
}

Rewrite search queries

Replace Find query APIs with Graph query APIs.

API mapping

Find APIGraph API
.Search<T>().QueryContent<T>()
.For().SearchFor()
.Filter().Where()
.Take().Limit()
.GetResult()await GetAsContentAsync()
.TotalMatching.Total

Graph queries require asynchronous execution.

Example query

var result = await _client.QueryContent<ISitePageData>()
    .SearchFor(query)
    .UsingFullText()
    .Limit(pageSize)
    .Skip((page - 1) * pageSize)
    .IncludeTotal()
    .GetAsContentAsync();

Update asynchronous methods

Optimizely Graph queries are asynchronous. Update calling methods to support async execution.

Example

Before:

public ActionResult Search(string q)
{
    var results = _searchService.Search(q);
    return View(results);
}

After:

public async Task<ActionResult> Search(string q)
{
    var results = await _searchService.SearchAsync(q);
    return View(results);
}

Replace UnifiedSearch

If the site uses UnifiedSearch, replace it with a Graph query using the interface created earlier.

Example:

var result = await _client.QueryContent<ISitePageData>()
    .SearchFor(query)
    .UsingFullText()
    .Limit(pageSize)
    .Skip(offset)
    .IncludeTotal()
    .GetAsContentAsync();

Update Razor views

Add the Graph tag helper.

Update _ViewImports.cshtml:

@addTagHelper *, Optimizely.Graph.AspNetCore

Add tracking setup to the layout:

<graph-tracking-setup />

Update search result links if click tracking is used.

Example:

<graph-trackable-link url="@Url.ContentUrl(hit.ContentLink)"
track-url="@Model.GetTrackUrl(hit)">
@hit.Name
</graph-trackable-link>

Remove remaining Find code

Search the project for any remaining Find references and remove them.

Examples include:

  • FindOptions
  • IFindStatisticsClient
  • SearchSection
  • Find initialization modules

Build and test

Build the project:

dotnet build

Run tests:

dotnet test

Start the site and verify that search works correctly.

dotnet run

Troubleshooting

If Search returns no results

Verify the following:

  • Graph credentials in appsettings.json are correct.
  • services.AddContentGraph() is registered.
  • Content has synchronized to Optimizely Graph.

You can inspect the generated GraphQL query:

var query = _client.QueryContent<ArticlePage>().Limit(10);
Console.WriteLine(query.ToGraphQL());

If total results return zero

Ensure the query includes:

.IncludeTotal()

Without this method, the total count is not returned.