Migrate best bets to pinned results
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 | targetKey: _metadata.key (CMS 13/SaaS) or ContentLink.GuidValue (earlier CMS versions) | Retrieve the version-appropriate targetKey for each content item |
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 | Per-item language field | Set language to null to apply the item to all languages, or create one pinned item per language to target specific ones |
Priority control | Implicit (insertion order) | Explicit priority field | Assign a priority value to each item. A higher number ranks higher |
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 |
📘 Note Both 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.Framework
- EPiServer.Find.Cms
- Optimizely.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();📘 Note Optimizely Graph supports internal CMS content only. You cannot migrate best bets that reference external URLs. Handle them separately.
Map content ID to Graph target key
Use a version-appropriate Graph query to retrieve the targetKey for each content item you plan to pin. The value from _metadata.key may itself be a GUID, but use that field as the source on Optimizely CMS 13 and Optimizely CMS (SaaS).
For Optimizely CMS 13 and Optimizely CMS (SaaS), pass the extracted ContentReference.ID values to a query that returns each matching ID with its _metadata.key:
query GetContentKeys($ids: [Int!]!) {
_Content(where: { ContentLink: { Id: { in: $ids } } }, limit: 100) {
items {
ContentLink {
Id
}
_metadata {
key
}
}
}
}For earlier CMS versions, query the legacy content ID and its ContentLink.GuidValue:
query GetContentIds($ids: [Int!]) {
Content(where: { ContentLink: { Id: { in: $ids } } }, limit: 100) {
items {
ContentLink {
Id
GuidValue
}
}
}
}Store the mapping between ContentReference.ID and the version-appropriate targetKey for later use.
If you have more than 100 IDs, run each lookup query in batches of 100.
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": "migrated-best-bets",
"isActive": true
}The key is a unique identifier of your choosing that contains no whitespace. Set isActive to true explicitly, because collections default to inactive.
Save the collection key for use in GraphQL queries, and the returned id for the REST paths that manage pinned items.
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": "<TARGET_KEY_FROM_VERSION_APPROPRIATE_QUERY>",
"language": "en",
"priority": 10,
"isActive": true
}phrases holds one phrase per item, matched case-insensitively against the whole query phrase. Create one item per phrase if a best bet mapped several phrases to the same content. Within a collection, the combination of phrases, targetKey, and language must be unique.
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,
"createdAt": "2026-09-30 10:12:04",
"updatedAt": "2026-09-30 10:12:04"
}
]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",
"collectionId": "COLLECTION GUID",
"phrases": "support",
"targetKey": "12345678-abcd-1234-abcd-1234567890ab",
"language": "en",
"priority": 10,
"isActive": true,
"createdAt": "2026-09-30 10:12:04",
"updatedAt": "2026-09-30 10:12:04"
}
]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 }
}
}