Migrate best bets to pinned results
Migrate Optimizely Search & Navigation best bets to Optimizely Graph pinned results, covers configuration differences, data mapping, API usage, and search query updates.
Best bets in Optimizely Search & Navigation and pinned results in Optimizely Graph promote specific content to the top of search results for defined search phrases. Pinning ensures that users see important or authoritative content before other results.
This guide explains how to migrate existing best bets from Optimizely Search & Navigation to Optimizely Graph pinned results. It covers API usage, data mapping, and query updates.
When to use pinned results
- Promote branded or navigational searches. For example, directing users to an About us page for company-related queries.
- Highlight campaign or seasonal content for specific keywords.
- Ensure critical support or contact information appears first in search results.
- Guide users to official or compliant content for regulatory or policy-related searches.
Key differences and migration impact
The following table summarizes the main differences between Search & Navigation best bets and Optimizely Graph pinned results and highlights required migration actions.
| Feature | Search & Navigation | Graph | Migration impact |
|---|---|---|---|
| Configuration | Admin UI | REST API only | Build an API-based integration (HMAC or Basic authentication required) |
| Architecture | Phrase-to-content mapping | Collection-based | Create collections, then add pinned items |
| Content reference | Integer ID | Content key | Query Graph to map ContentReference.ID to the content key. The key format depends on your schema. See Map content ID to Graph content key |
| Supported content | Internal and external URLs | Internal content only | Migrate internal content; handle external URLs separately |
| Title and description | Custom or derived from content | Not supported | Use content fields in the application layer |
| Display styling | Configurable in UI | Not supported | Implement styling in the application layer |
| Language handling | Single item can apply to all languages | Separate item per language | Create one pinned item per language |
| Priority control | Implicit (insertion order) | Explicit priority field | Assign priorities sequentially (1 is highest) |
| Result limit | Unlimited | Maximum of five | Select the five most important results |
| Query syntax | .ApplyBestBets() | pinned parameter | Update all search queries |
| Locale handling | Automatic | Explicit | Always specify locale in queries |
| Status | Delete to remove | Active or inactive | Use status for temporary or seasonal items |
NoteBoth Optimizely Search & Navigation and Optimizely Graph support case-insensitive phrase matching.
Migration workflow
Retrieve best bets from Search & Navigation
Use the IBestBetRepository interface to retrieve existing best bets. You must have the following prerequisites:
EPiServer.Find.FrameworkEPiServer.Find.CmsOptimizely.ContentGraph.Core
using EPiServer.Find;
using EPiServer.Find.Framework.BestBets;
using EPiServer.Find.Cms;
var allBestBets = _bestBetRepository.List().ToList();
var internalBestBets = allBestBets
.Where(bb => bb.BestBetSelector is PageBestBetSelector)
.ToList();
NoteOptimizely Graph supports internal CMS content only. You cannot migrate best bets that reference external URLs. Handle them separately.
Map content ID to Graph content key
Optimizely Graph identifies content by a key instead of an integer content ID. Query Graph to retrieve the key for each item you plan to pin.
The key format depends on which schema your instance exposes. Optimizely Graph matches targetKey against the stored key exactly. It does not normalize the value you send, so use the format your schema exposes.
| Schema | Query for the key | Key format |
|---|---|---|
| Native Graph schema, CMS 13 and CMS (SaaS) | _metadata { key } | 32-character GUID with no hyphens, for example 2e684df21e5c4daa8496267cd69485e4 |
| Synchronization client schema (CMS 12) | ContentLink { GuidValue } | GUID with hyphens, for example 2e684df2-1e5c-4daa-8496-267cd69485e4 |
On CMS 13 and CMS (SaaS), query _metadata.key:
query GetContentKeys($ids: [Int!]) {
Content(where: { ContentLink: { Id: { in: $ids } } }, limit: 100) {
items {
ContentLink {
Id
}
_metadata {
key
}
}
}
}On CMS 12, query ContentLink.GuidValue:
# CMS 12 equivalent
query GetContentIds($ids: [Int!]) {
Content(where: { ContentLink: { Id: { in: $ids } } }, limit: 100) {
items {
ContentLink {
Id
GuidValue
}
}
}
}Store the mapping between ContentReference.ID and the content key for later use. You need the key to set targetKey on each pinned item.
ImportantDo not use
_idas thetargetKey. The_idfield joins the content key, the language, and the status with underscores. For example,2e684df21e5c4daa8496267cd69485e4_en_Publishedidentifies one language and status variant, not the content item. Optimizely Graph accepts any string as atargetKeyand returns no error. It stores a pinned item created with_id, or with a hyphenated GUID on the native Graph schema, but the item never matches any content. Use thelanguagefield on the pinned item to target a specific locale.
Create a pinned results collection
Create a collection to group related pinned results using the following endpoint:
POST https://cg.optimizely.com/api/pinned/collections
{
"title": "Migrated Best Bets",
"key": "COLLECTION_GUID",
"isActive": true
}Save the collection key for use in GraphQL queries.
Add pinned items to the collection
Add each migrated best bet as a pinned item using the following endpoint:
POST https://cg.optimizely.com/api/pinned/collections/{id}/items
{
"phrases": "support",
"targetKey": "2e684df21e5c4daa8496267cd69485e4",
"language": "en",
"priority": 1
}Set targetKey to the content key you stored in Map content ID to Graph content key. The preceding example uses the native Graph schema format. On CMS 12, use the hyphenated ContentLink.GuidValue instead.
Update application search queries
Replace Search & Navigation-based search queries with Graph queries that use the pinned parameter.
query SearchWithPinnedResults(
$term: String!
$collection: String!
$locale: [Locales!]
) {
Content(
where: { _fulltext: { contains: $term } }
pinned: { phrase: $term, collections: [$collection] }
locale: $locale
) {
items {
Name
Url
}
}
}
Pinned results appear first in the response, followed by regular search results.
Verify and test
Verify collections and pinned items using the REST API or Swagger UI, then validate search behavior using GraphiQL.
Remove the pinned parameter to compare results with and without pinned content.
Verify with REST API
Use the Swagger UI or direct API calls to verify your migration.
Use the following endpoint to get all collections:
GET https://cg.optimizely.com/api/pinned/collections
// Requires HMAC or Basic AuthThe following is an example response to get all collections:
[
{
"id": "COLLECTION GUID",
"title": "Migrated Best Bets",
"key": "migrated-best-bets",
"isActive": true
}
]Use the following endpoint to get items in a collection:
GET https://cg.optimizely.com/api/pinned/collections/{id}/items
// Requires HMAC or Basic AuthThe following is an example response to get items in a collection:
[
{
"id": "ITEM_GUID",
"phrases": "support",
"targetKey": "2e684df21e5c4daa8496267cd69485e4",
"language": "en",
"priority": 1,
"isActive": true
}
]The API returns targetKey in the same format you supplied it. When a pinned item does not take effect, compare the returned targetKey with the content key from Graph. Confirm that the two match exactly.
Test with GraphQL
Test using GraphiQL at https://cg.optimizely.com/app/graphiql?auth=YOUR_SINGLE_KEY.
The following example query returns pinned support content first:
query {
Content(
where: { _fulltext: { contains: "support" } }
pinned: {
phrase: "support"
collections: ["migrated-best-bets"] # Use collection key, not ID
# collections: ["water", "chemistry"] # Multiple collections supported
}
locale: en
) {
items { Name Url }
}
}Updated 4 days ago
