Skip to content

Configure a Custom Search Provider

⏱ 25 minutes advanced

The default CMS search queries the content database directly. This works for small sites but does not scale for full-text search, faceted filtering, or relevance ranking across thousands of pages. A custom search provider lets you route queries to a dedicated search engine while keeping the same CMS search API.

  1. Implement the SearchProvider base class
  2. Build an indexing pipeline that syncs content to your search backend
  3. Register the provider with the CMS
  4. Test search results in the editor

Create a class that extends SearchProvider and implements the search logic.

Custom search provider
csharp
using Optimizely.Cms.Core;
using Optimizely.Cms.Core.Search;
using Optimizely.Cms.Core.Repositories;

namespace MySite.Search;

public class ElasticSearchProvider : SearchProvider
{
    private readonly IElasticClient _elastic;
    private readonly IContentRepository _contentRepo;

    public override string Area => "CMS/pages";
    public override string Category => "Pages";

    public ElasticSearchProvider(
        IElasticClient elastic,
        IContentRepository contentRepo)
    {
        _elastic = elastic;
        _contentRepo = contentRepo;
    }

    public override SearchResults Search(Query query)
    {
        var response = _elastic.Search<SearchDocument>(s => s
            .Query(q => q
                .MultiMatch(mm => mm
                    .Query(query.SearchQuery)
                    .Fields(f => f
                        .Field("title", boost: 2.0)
                        .Field("body")
                        .Field("summary"))))
            .From(query.Start)
            .Size(query.MaxResults));

        return new SearchResults
        {
            TotalHits = (int)response.Total,
            Results = response.Documents
                .Select(MapToSearchResult)
                .ToList()
        };
    }

    private SearchResult MapToSearchResult(
        SearchDocument doc) => new()
    {
        Title = doc.Title,
        Url = doc.Url,
        PreviewText = doc.Summary,
        ContentLink = ContentReference.Parse(doc.ContentId)
    };
}

Create a content event handler that updates the search index whenever content is published or deleted.

Search indexer using content events
csharp
using Optimizely.Cms.Core;
using Optimizely.Cms.Core.Events;
using Optimizely.Cms.Framework.Initialization;
using Optimizely.Cms.Core.Initialization;

namespace MySite.Search;

[InitializableModule]
[ModuleDependency(typeof(CmsCoreInitialization))]
public class SearchIndexingModule : IInitializableModule
{
    public void Initialize(InitializationEngine context)
    {
        var events = context.Locate.Advanced
            .GetInstance<IContentEvents>();
        events.PublishedContent += OnPublished;
        events.DeletedContent += OnDeleted;
    }

    private void OnPublished(
        object? sender, ContentEventArgs e)
    {
        if (e.Content is PageData page)
        {
            var indexer = ServiceLocator.Current
                .GetInstance<ISearchIndexer>();
            indexer.IndexContent(page);
        }
    }

    private void OnDeleted(
        object? sender, ContentEventArgs e)
    {
        var indexer = ServiceLocator.Current
            .GetInstance<ISearchIndexer>();
        indexer.RemoveFromIndex(e.ContentLink);
    }

    public void Uninitialize(InitializationEngine context)
    {
        var events = context.Locate.Advanced
            .GetInstance<IContentEvents>();
        events.PublishedContent -= OnPublished;
        events.DeletedContent -= OnDeleted;
    }
}
Search document for the index
csharp
namespace MySite.Search;

public class SearchDocument
{
    public string ContentId { get; set; } = "";
    public string Title { get; set; } = "";
    public string Body { get; set; } = "";
    public string Summary { get; set; } = "";
    public string Url { get; set; } = "";
    public string ContentType { get; set; } = "";
    public DateTime LastModified { get; set; }
}

Register your search provider in an initialization module so the CMS uses it for editor search.

Register the search provider
csharp
using Optimizely.Cms.Framework.Initialization;
using Microsoft.Extensions.DependencyInjection;

namespace MySite.Search;

[InitializableModule]
public class SearchRegistrationModule : IConfigurableModule
{
    public void ConfigureContainer(
        ServiceConfigurationContext context)
    {
        context.Services
            .AddSingleton<ISearchIndexer, ElasticSearchIndexer>();
        context.Services
            .AddSingleton<SearchProvider, ElasticSearchProvider>();
    }

    public void Initialize(InitializationEngine context) { }
    public void Uninitialize(InitializationEngine context) { }
}

After deploying:

  1. Open the CMS editor and use the search box in the content tree
  2. Type a query and verify results come from your search backend
  3. Publish a new page, then search for it. It should appear within seconds.
  4. Delete a page and confirm it no longer appears in results.
IssueCauseFix
No search resultsIndex empty or connection failedVerify the search backend connection and run a full reindex
Stale results after publishEvent handler not triggeringCheck [InitializableModule] and event subscription
Editor search still uses defaultProvider not registeredVerify DI registration in ConfigureContainer