---
title: Migrate best bets to pinned results
slug: graph/docs/migrate-best-bets-to-pinned-results
docTags: 
createdAt: 2026-07-30T20:30:19.480Z
---

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                                                                                  |

:::hint{type="info"}
**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`

```csharp
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();
```

:::hint{type="info"}
**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`:

```graphql
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`:

```graphql
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:

```json
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:

```json
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.

```graphql
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](https://cg.optimizely.com/app/swagger) or direct API calls to verify your migration.

Use the following endpoint to get all collections:

```http
GET https://cg.optimizely.com/api/pinned/collections
// Requires HMAC or Basic Auth
```

The following is an example response to get all collections:

```json
[
  {
    "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:

```http
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:

```json
[
  {
    "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](https://cg.optimizely.com/app/graphiql?auth=YOUR_SINGLE_KEY).

The following example query returns pinned support content first:

```graphql
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 }
  }
}
```
