Blog / Integrations

Searching Content with Kentico Cloud: Algolia Integration

by Martin Dobsicek

Dec 14, 2017

Search is an important part of websites as it helps your visitors find their desired content quickly. With Kentico Cloud, you can easily integrate external search engines so don’t need to implement a complicated search algorithm yourself.

In this blog post, I’m going to walk you through a sample integration of the Kentico Cloud headless CMS with Algolia. In fact, we implemented the Algolia search on the Kentico Cloud blog in September.

We’re going to develop the functionality on the .NET Core platform using the MVC approach, and using the C# programming language for the back end and JavaScript for the front end. It will search through the content stored in Kentico Cloud. In this case, we’ll use blog posts as the content type to be searched in code examples, but the code can be easily modified for any content type of your choice.

Getting Started with Algolia

The first step in the process is to create an account with Algolia. Once registered, you can log in and find the API keys in your profile. There are three basic pieces of information that you need to use when calling Algolia’s API:

  • Application ID – to identify the application we want to work with
  • Search-Only API Key – the API key usable for search queries (can be visible publicly so it can be used in the front end)
  • Admin API Key – for managing indexes (needs to be kept private so it’s available in the back end only)

I would also recommend going through Algolia’s documentation to get familiar with Algolia’s concepts, API, and prepared UI widgets.

Now there are three basic steps to implementing the code:

  • Index current data
  • Ensure indexing when data changes (using a webhook)
  • Search through the index and display results (when web visitors submit a query)

Index Current Data

Data should be indexed from the back end since indexing uses the private Admin API Key. We’ll use the Algolia.Search NuGet package, to ensure smooth working with Algolia’s API, and the Kentico Cloud Delivery .NET SDK, to acquire the blog post data from the Kentico Cloud app. 

Firstly, we prepare a model called BlogPostSearchModel in which we store the data of the blog posts. The model needn’t only contain texts to be indexed, we can add various other data to the Algolia index for use later on in displaying results. By doing this, you can save time and don’t have to first obtain the data from the Kentico Cloud application. For our example, we’ll add the URLSlug field to be able to create links to blog posts.

public class BlogPostSearchModel 
	public string ID { get; set; }
	public string UrlSlug { get; set; }
	public string Title { get; set; }
	public string Body { get; set; }
	public string Topic { get; set; }
	public string Author { get; set; }

Then we prepare the method for creating BlogPostSearchModel objects from data returned by the Delivery API as an object of the ContentItem type.

private static BlogPostSearchModel GetBlogPost(ContentItem item)
	if (item == null)
		return new BlogPostSearchModel();
	return new BlogPostSearchModel
		ID = item.System.Id,
		UrlSlug = item.GetString("url_slug"),
		Title = item.GetString("title"),
		Body = item.GetString("body"),
		Topic = item.GetOptions("topic").FirstOrDefault()?.Name,
		Author = item.GetModularContent("author").FirstOrDefault()?.GetString("name")

After that, we create a method that acquires all blog posts using the Delivery API and prepares a list of the BlogPostSearchModel instances leveraging the method above. We use ProjectID to identify the project from which to get blog posts.

private static async Task<IEnumerable<BlogPostSearchModel>> GetBlogPosts()
	var parameters = new List<IQueryParameter>
		new EqualsFilter("system.type", "blog_post"),
		new OrderParameter("", SortOrder.Descending)

	DeliveryClient client = new DeliveryClient("<yourDeliveryApiProjectID>");
	var response = await client.GetItemsAsync(parameters);

	return response.Items.Select(GetBlogPost);

This is a basic approach to acquiring data from Kentico Cloud, which I’ve chosen for demonstration purposes. For production code, I would suggest using a strongly typed models approach instead, as it is the recommended way of receiving content via the Delivery API.

We’ll also need a method that will sanitize the content of rich text fields, i.e. remove HTML tags and escape any special characters that might cause an error during indexing.

private static string SanitizeRichText(string inputString)
	// Remove HTML tags
	inputString = Regex.Replace(inputString, "<.*?>", string.Empty);

	// Decode/Escape special characters
	return Regex.Replace(inputString, "&nbsp;", " ").Replace("\"", " ").Replace("\\", "\\\\");

Now we can implement a method for indexing the given blog posts in Algolia’s indexto be used both for initial indexing and future data indexing.

public static void IndexBlogPostsInAlgolia(IEnumerable<BlogPostSearchModel> blogPostsToIndex, bool isInitialIndexing)
	AlgoliaClient algoliaClient = new AlgoliaClient("<yourAlgoliaApplicationID>", "<yourAlgoliaAdminApiKey>");

	var objs = new List<JObject>();
	foreach (var blogPost in blogPostsToIndex)
	var algoliaIndex = algoliaClient.InitIndex("<yourIndexName>");

	if (isInitialIndexing)
		algoliaIndex.SetSettings(JObject.Parse(@"{""searchableAttributes"":[""title"", ""body"", ""topic"", ""author""]}"));

	var res = algoliaIndex.AddObjects(objs);

Finally, we can leverage these methods to acquire our current blog posts and index them in Algolia.

var blogPosts = await GetBlogPosts();
IndexBlogPostsInAlgolia(blogPosts, true);

Ensure Indexing When Data Changes

Kentico Cloud supports webhooks, which can notify your system whenever you publish new content or edit published content. To enable this, you need to set up a webhook as described in the documentation and implement an MVC action method that will handle the message from the webhook to the system. 

This method will handle only HttpPost requests on the particular route specified in the “URL address” field in our webhook setting. (For example, if we use the “webhook” route, the URL should be “https://ourdomain/webhook”.) We should also check that the message wasn’t modified on its way. The signature, sent along with the message, contains a base64 encoded hash of the message. So, first you need to prepare a method that will compute the expected signature.

protected string ComputeExpectedSignature(string message)
	const string secret = "<hereYouShouldPlaceSecretFromWebhook'sSetting>";
	var messageBytes = Encoding.UTF8.GetBytes(message);
	var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
	return Convert.ToBase64String(hmac.ComputeHash(messageBytes));

Then, we can implement the action method that will handle the webhook messages:

public async Task<IActionResult> ProcessWebhookMessage([FromHeader(Name = "X-KC-Signature")]string signature)
	const string typeToProcess = "blog_post";

	if (Request.Body.CanSeek)
		Request.Body.Position = 0;
	var contentString = new StreamReader(Request.Body).ReadToEnd();
	if (signature.Equals(ComputeExpectedSignature(contentString)))
		var message = JsonConvert.DeserializeObject<WebhookMessage>(contentString); 
		var codeNames = message.Data.Items
							.Where(item => typeToProcess.Equals(item.Type))
							.Select(item => item.Codename);
		if (codeNames.Any())
				var parameters = new List<IQueryParameter>
					new InFilter("system.codename", codeNames.ToArray()),
					new EqualsFilter("system.type", type)
				var response = await mService.WebhooksClient.GetItemsAsync(parameters);
				var blogPosts = response.Items.Select(GetBlogPost);
				IndexBlogPostsInAlgolia(blogPosts, false);
			catch (AggregateException e)
				// Log exception
	return Ok();

Search and Display Results

We’ll be performing a search on the front-end side so that we can then call Algolia’s servers and receive a response directly without calling back to our servers. This approach should ensure much better performance. We can also leverage existing Algolia widgets for easier implementation of the UI. So how will we build our UI?

First of all, we need to prepare an input field into which users can enter their search queries. Then we can add some divs that will act as containers for other UI parts like statistics (how many results were found), pager, and (mainly) search results.

	<input type="search" id="blog-search-input" placeholder="Search blog posts" name="search" autocomplete="off" autofocus />
<div id="stats"></div>
<div id="hits"></div>
<div id="pagination"></div>

The layout of the particular item in results is defined by a template. The template consists of HTML code placed within the <script> tag (so it’s not rendered to a page) with an ID “hit-template”. There are two types of variables that can be used in the template which Algolia’s script automatically resolves:

  • Variable within two pairs of curly brackets—these are properties from the index.
  • Variable within three pairs of curly brackets—where we reference the _highlightResult object which contains properties from the index, with HTML highlights on the matched words.
<script type="text/html" id="hit-template">
            <a href="blog/{{urlSlug}}" class="blog-search-results__link">
                <div class="blog-search-results__image js-fix-image" style="background-image: url('{{image}}');">
                <div class="blog-search-results__text">
                    <h3 class="blog-search-results__title">{{{_highlightResult.title.value}}}</h3>

                    <div class="blog-search-results__desc">
                    <div class="blog-search-results__info">
                        <div class="blog-search-results__author">
                            {{author}} <span class="blog-search-results__bullet">•</span> {{published}}
                        <div class="blog-search-results__topic">

Now we can add JavaScript that will show the results and the additional UI elements in the appropriate containers.

<script src=""></script>
    // Name of search index on Algolia
    var blogIndexName = '<yourIndexName>';

    // Initialize "instantsearch" object for your index. It will act as context for particular widgets
    var search = instantsearch({
        appId: '@(algoliaSettings.Value.AlgoliaAppId)',
        apiKey: '@(algoliaSettings.Value.AlgoliaSearchApiKey)', // search only API key, no ADMIN key //AppOptions.AlgoliaSearchApiKey
        indexName: blogIndexName,
        urlSync: true,

    // Bind "search box" widget to appropriate input
            container: '#blog-search-input',
            autofocus: true

    // Bind "Statistics" widget to appropriate container
            container: '#stats',
            templates: {
                body: '<p><strong>{{nbHits}}</strong> results found for "<strong>{{query}}</strong>"</p>'

    // Bind "Hits" widget to appropriate container. It displays results using specified template
            container: '#hits',
            hitsPerPage: 9,
            templates: {
                item: document.getElementById('hit-template').innerHTML,
                empty: "We didn't find any results for the search <em>\"{{query}}\"</em>"

    // Bind "Pagination" widget to appropriate container.
    // We set "previous" and "next" captions to empty string as we will use arrow icons defined by CSS styles instead
            container: '#pagination',
            showFirstLast: false,
            labels: { previous: '', next: '' }


Wrapping up

In this walkthrough, I’ve showed you how easily you can integrate Kentico Cloud with Algolia to enable searching of your website content. This functionality will help improve user experience. 

Are you going to give Algolia search a try?

Have you already checked out the search on the Kentico Cloud blog? What do you think? 

Let us know in the forum below!