Xperience by Kentico + Elasticsearch – fast full-text search for your CMS

04. 11. 2025

Search has become a key element of nearly every web application today. Xperience by Kentico offers several ways to efficiently perform full-text search and filter content managed in the CMS.

Besides paid cloud solutions such as Azure AI Search, Algolia, or Recombee, the only free on-premise option used to be Lucene-based search integration. That’s what motivated us to implement Elasticsearch as an alternative tool with on-premise hosting support. Elasticsearch is available free of charge under the Elastic License 2.0.

Elasticsearch is a distributed system built on Apache Lucene.
✅ It offers high scalability,
✅ supports full-text search across large datasets,
✅ and enables advanced filtering and aggregations.

With an on-premise setup, you and your clients retain full control over data, performance, and security—without relying on third-party cloud services.

Elasticsearch for Xperience by Kentico

Connecting an on-premise Elasticsearch instance can be done in a few simple steps. In this article, we’ll cover how to set up Elasticsearch, configure indexing and data mapping from Kentico Xperience, and implement search.

1. Installing packages

First, add the NuGet package. Run the following command in your terminal:

dotnet add package XperienceCommunity.ElasticSearch

2. Elasticsearch configuration

Next, add the following configuration to your app’s appsettings.json, including the endpoint of the running Elasticsearch instance and authentication details. You can authenticate either with a username/password or an API key generated in Kibana.

 "SearchServiceEnabled"true,

 "SearchServiceEndPoint""<your index application url>"//Endpoint of the running Elasticsearch instance

 "SearchServiceAPIKey""<your API Key for Elasticsearch>"

 }

Alternatively, use a username and password instead of an API key:

"CMSElasticSearch": {
 // ...
 // ...
 "SearchServiceUsername": "<your index application username>",
 "SearchServicePassword": "<your index application password>",
 }

3. Creating the model and strategy

The library’s core functionality is based on a custom indexing strategy that fully adapts to your content model and desired search behavior. This strategy lets you precisely define which data is indexed, how it’s mapped to Elasticsearch, and how it reacts to content changes. Below are the steps to configure the process using the provided interfaces and methods.

Custom index model

Define your own search model by extending BaseElasticSearchModel provided by the library. This model will be used to create the index in Elasticsearch.

public class DancingGoatSearchModel : BaseElasticSearchModel
{
    public string Title { get; set; }
 
    public string Content { get; set; }
}

Implementing the Indexing Strategy

Create a custom implementation of BaseElasticSearchIndexingStrategy<TSearchModel> to tailor how web page items or content items are processed for indexing.

public class DancingGoatSearchStrategy(...) : BaseElasticSearchIndexingStrategy<DancingGoatSearchModel>
{
    ...
}

Configuring fields (TypeMapping)

Define how individual fields are stored in the Elasticsearch index. Override Mapping(TypeMappingDescriptor<TSearchModel> descriptor). This method lets you define data types and behavior—e.g., whether a field is used for full-text search (text) or exact filtering (keyword).

public override void Mapping(TypeMappingDescriptor<DancingGoatSearchModel> descriptor) =>
    descriptor
        .Properties(props => props
            .Keyword(x => x.Title)
            .Text(x => x.Content));

You can find the complete list of field types in the official Elasticsearch documentation.

4. Mapping content to the search model

Next, define how content properties map to your custom index model.

Override Task<IElasticSearchModel?> MapToElasticSearchModelOrNull(IIndexEventItemModel item) and implement mapping to your BaseElasticSearchModel-based class (here: DancingGoatSearchModel). Common base properties defined in BaseElasticSearchModel are mapped automatically—so you only need to handle custom fields for a given content type.

The example below maps an ArticlePage with an ArticleTitle property and the page’s raw content to DancingGoatSearchModel:

public override async Task<IElasticSearchModel?> MapToElasticSearchModelOrNull(IIndexEventItemModel item)
{
    var result = new DancingGoatSearchModel();
 
    if (item is not IndexEventWebPageItemModel indexedPage)
    {
        return null;
    }
 
    if (string.Equals(item.ContentTypeName, ArticlePage.CONTENT_TYPE_NAME, StringComparison.OrdinalIgnoreCase))
    {
        var page = await strategyHelper.GetPage<ArticlePage>(
            indexedPage.ItemGuid,
            indexedPage.WebsiteChannelName,
            indexedPage.LanguageName,
            ArticlePage.CONTENT_TYPE_NAME);
 
        if (page is null)
        {
            return null;
        }
 
        result.Title = page.ArticleTitle ?? string.Empty;
        var rawContent = await webCrawler.CrawlWebPage(page!);
        result.Content = htmlSanitizer.SanitizeHtmlDocument(rawContent);
    }
 
    return result;
}

IIndexEventItemModel is an abstract type representing an item processed for indexing. It includes IndexEventWebPageItemModel for web pages and IndexEventReusableItemModel for reusable content items.

Data retrieval for indexing depends on your implementation. For example, you can use a generic GetPage<T> method as shown in the example.

5. Updating related content in the index

Direct manipulation with a specific CMS item automatically triggers linked events, ensuring that the corresponding record in the index remains up to date.

However, if a related content item changes — for example, a reusable element that appears on multiple pages — you need to implement logic that determines which other items in the index must be reindexed.

For this purpose, use the FindItemsToReindex method.
All items returned by this method will be passed to MapToElasticSearchModelOrNull(IIndexEventItemModel item) for indexing.
This method is key to maintaining data consistency in the index when linked content changes.

An example of this implementation can be found in the documentation.

6. Registering the strategy

To make your custom strategy usable, register it via dependency injection (DI):

services.AddKenticoElasticSearch(builder =>
{
    builder.RegisterStrategy<DancingGoatSearchStrategy, DancingGoatSearchModel>(nameof(DancingGoatSearchStrategy));
}, configuration);

7. Setting up the index in Xperience

The next step is to create the index itself in the Xperience administration.
This is done in the Elastic Search application added to the system by this library.
Here, you define the index name, select the appropriate strategy, choose language variants, channels, and content types to be indexed.

After creating and configuring the index, perform a Rebuild action on the List of registered Elastic Search indices page.

After this action, the index should already be filled with items (according to your DancingGoatSearchStrategy implementation) and ready for search and filtering.

📘 Documentation: Managing Indexes

8. Searching

The final step is implementing the search itself.

Searching is performed using the IElasticSearchQueryClientService, which allows you to set search parameters and retrieve data from the Elasticsearch index.

    var index = searchClientService.CreateSearchClientForQueries(indexName);
 
    page = Math.Max(page, 1);
    pageSize = Math.Max(1, pageSize);
 
    var request = new SearchRequest(indexName)
    {
        From = (page - 1) * pageSize,
        Size = pageSize,
        Query = string.IsNullOrEmpty(searchText)
            ? new MatchAllQuery()
            : new MultiMatchQuery()
            {
                Fields = new[]
                {
                    nameof(DancingGoatSearchModel.Title).ToLower(),
                },
                Query = searchText,
            },
        TrackTotalHits = new TrackHits(true)
    };
 
    var response = await index.SearchAsync<DancingGoatSearchModel>(request);

The implementation uses the standard ElasticsearchClient (.NET Client v8) with the option to use either Fluent API or Object Initializer API.
You can see the difference between these approaches in the official documentation.

Conclusion

Integrating Elasticsearch into Xperience by Kentico expands search capabilities and offers flexibility not available with other full-text tools.
This integration enables fast indexing, advanced querying, and on-premise hosting — a huge advantage for users who want full control over their data.

As the technology evolves, Elasticsearch will likely become an even more attractive solution for search requirements within Xperience by Kentico.

We hope this article helped you better understand how to integrate Elasticsearch with Xperience by Kentico.
However, the possibilities go even further — the documentation also includes examples for crawling pages or managing index aliases.

A more detailed guide on creating a custom indexing strategy (including code samples), data mapping, and integration with Kentico Xperience can be found in the official library repository.

💡 Want to improve search on your website or portal?
Bluesoft can help you implement Elasticsearch for Kentico Xperience — from architecture design to final deployment.
👉 Contact us and find out how to get the most out of your data.

Do you want to know more? Ask us a question.

It is important for us to have a direct contact with our clients. We can consult with you both in-person and online. Please do not hesitate to contact us with your inquiry or further specification of your request.

Petr Lebeda
Petr Lebeda Sales & Consulting Manager +420 723 484 557