Dev guideRecipesAPI ReferenceChangelog
Dev guideRecipesUser GuidesNuGetDev CommunityOptimizely AcademySubmit a ticketLog In
Dev guide

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.

FeatureSearch & NavigationGraphMigration impact
ConfigurationAdmin UIREST API onlyBuild an API-based integration (HMAC or Basic authentication required)
ArchitecturePhrase-to-content mappingCollection-basedCreate collections, then add pinned items
Content referenceInteger IDContent keyQuery 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 contentInternal and external URLsInternal content onlyMigrate internal content; handle external URLs separately
Title and descriptionCustom or derived from contentNot supportedUse content fields in the application layer
Display stylingConfigurable in UINot supportedImplement styling in the application layer
Language handlingSingle item can apply to all languagesSeparate item per languageCreate one pinned item per language
Priority controlImplicit (insertion order)Explicit priority fieldAssign priorities sequentially (1 is highest)
Result limitUnlimitedMaximum of fiveSelect the five most important results
Query syntax.ApplyBestBets()pinned parameterUpdate all search queries
Locale handlingAutomaticExplicitAlways specify locale in queries
StatusDelete to removeActive or inactiveUse 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 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.

SchemaQuery for the keyKey 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.

🚧

Important

Do not use _id as the targetKey. The _id field joins the content key, the language, and the status with underscores. For example, 2e684df21e5c4daa8496267cd69485e4_en_Published identifies one language and status variant, not the content item. Optimizely Graph accepts any string as a targetKey and 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 the language field 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 Auth

The 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 Auth

The 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 }
  }
}


Did this page help you?