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 previewThis 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:
| Area | CMS 12 (Find) | CMS 13 (Graph) |
|---|---|---|
| NuGet packages | EPiServer.Find, EPiServer.Find.Cms | Optimizely.Graph.Cms.Query, Optimizely.Graph.AspNetCore |
| Search client | IClient / SearchClient.Instance | IGraphContentClient |
| Namespaces | EPiServer.Find.* | Optimizely.Graph.Cms.Query |
| Configuration | EPiServer:Find | Optimizely:Graph |
| Execution model | Mostly synchronous | Asynchronous (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-migrationUpgrade 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 buildResolve 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 runVerification 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 testCommit 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
NoteBuild 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 namespace | Graph namespace |
|---|---|
EPiServer.Find | Optimizely.Graph.Cms.Query |
EPiServer.Find.Api | Optimizely.Graph.Cms.Query |
EPiServer.Find.Cms | Optimizely.Graph.Cms.Query |
EPiServer.Find.Framework | Optimizely.Graph.Cms.Query |
Remove unused namespaces such as:
EPiServer.Find.Framework.StatisticsEPiServer.Find.Helpers.TextEPiServer.Find.UIWangkanai.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 API | Graph 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
UnifiedSearchIf 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.AspNetCoreAdd 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:
FindOptionsIFindStatisticsClientSearchSection- 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.jsonare 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.
Updated 11 days ago
