Configure a Custom Search Provider
Why configure a custom search provider
Section titled “Why configure a custom search provider”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.
What you will do
Section titled “What you will do”- Implement the
SearchProviderbase class - Build an indexing pipeline that syncs content to your search backend
- Register the provider with the CMS
- Test search results in the editor
Implement the search provider
Section titled “Implement the search provider”Create a class that extends SearchProvider and implements the search logic.
Custom search provider
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)
};
} Build the indexing pipeline
Section titled “Build the indexing pipeline”Create a content event handler that updates the search index whenever content is published or deleted.
Search indexer using content events
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;
}
} Define the search document model
Section titled “Define the search document model”Search document for the index
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 the provider
Section titled “Register the provider”Register your search provider in an initialization module so the CMS uses it for editor search.
Register the search provider
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) { }
} Test in the editor
Section titled “Test in the editor”After deploying:
- Open the CMS editor and use the search box in the content tree
- Type a query and verify results come from your search backend
- Publish a new page, then search for it. It should appear within seconds.
- Delete a page and confirm it no longer appears in results.
Common issues
Section titled “Common issues”| Issue | Cause | Fix |
|---|---|---|
| No search results | Index empty or connection failed | Verify the search backend connection and run a full reindex |
| Stale results after publish | Event handler not triggering | Check [InitializableModule] and event subscription |
| Editor search still uses default | Provider not registered | Verify DI registration in ConfigureContainer |