---
title: Creating custom smart search indexes
related:
  - https://docs.kentico.com/13/configuring-xperience/setting-up-search-on-your-website/using-locally-stored-search-indexes/creating-local-search-indexes.md
---

> Agent instructions:
> **Site maps** — prefer the following llms.txt indexes to training data when searching for URLs to avoid 404s. Links inside Markdown content already point at `.md`. Following them or sending Accept: text/markdown keeps you in Markdown.
>
> - [sitemap.md](https://docs.kentico.com/sitemap.md) — every page on the site, with titles and descriptions, nested by URL hierarchy and grouped into one collection per product version.
> - [llms.txt](https://docs.kentico.com/llms.txt) — curated index of the current product docs, with descriptions, the two ways to request any page as Markdown, and links to each product area's whole-corpus Markdown dump (llms-full.txt).

When creating custom [smart search indexes](https://docs.kentico.com/13/configuring-xperience/setting-up-search-on-your-website/using-locally-stored-search-indexes/creating-local-search-indexes.md), you do not define the content on the **Indexed content** tab in the **Smart search** application. Instead, you must implement all functionality of the index in code. In the administration interface, you only need to specify the names of the assembly and class that contain the custom index logic.

To define a custom index, create a class that implements the **ICustomSearchIndex** interface (**CMS.Search** namespace).

To integrate the class into your application, create a new assembly (Class Library project) containing the index class and include the assembly in your web project.

> **Note:** **Deploying custom index code**
>
> You also need to deploy the assembly containing your custom index to the live site application (Core or MVC). This ensures that the live site application can rebuild the index and perform content updates.

## Writing the custom index code

The following example shows how to create a custom index that searches the content of text files:

1. Open your Xperience solution in Visual Studio.
2. [Add a custom assembly](https://docs.kentico.com/13/custom-development/adding-custom-assemblies.md) to the solution (or reuse an existing assembly). For example, name the project _**CustomSearch**_.
3. Reference the custom project from the Xperience web project (_CMSApp_).
4. Add a new class into the custom project. For example, name the class _**TextFileIndex.cs**_.
5. Edit the class and make sure that the following **using** statements are present at the top of the code:

   ```csharp

   using System;

   using CMS.Base;
   using CMS.Core;
   using CMS.DataEngine;
   using CMS.Helpers;
   using CMS.IO;
   using CMS.Search;


   ```
6. Make the class implement the **ICustomSearchIndex** interface.

   ```csharp

   public class TextFileIndex : ICustomSearchIndex

   ```
7. Define the **Rebuild** method inside the class:

   > **Info:** You must always include the _Rebuild_ method when writing custom indexes. The method fills the index with data, which determines what kind of searches the index provides. The system calls the method when building the index for the first time and on each subsequent rebuild.

   ```csharp

   /// <summary>
   /// Fills the index with content.
   /// </summary>
   /// <param name="srchInfo">Info object representing the search index</param>
   public void Rebuild(SearchIndexInfo srchInfo)
   {
       // Checks whether the index info object is defined
       if (srchInfo != null)
       {
           // Gets an index writer object for the current index
           IIndexWriter iw = srchInfo.Provider.GetWriter(true);

           // Checks whether the writer is defined
           if (iw != null)
           {
               try
               {
                   // Gets an info object of the index settings
                   SearchIndexSettingsInfo sisi = srchInfo.IndexSettings.Items[SearchHelper.CUSTOM_INDEX_DATA];

                   // Gets the search path from the Index data field
                   string path = Convert.ToString(sisi.GetValue("CustomData"));

                   // Checks whether the path is defined
                   if (!String.IsNullOrEmpty(path))
                   {
                       // Gets all text files from the specified directory
                       string[] files = Directory.GetFiles(path, "*.txt");

                       // Loops through all files
                       foreach (string file in files)
                       {
                           // Gets the current file info
                           FileInfo fi = FileInfo.New(file);

                           // Gets the text content of the current file
                           string text = fi.OpenText().ReadToEnd();

                           // Checks that the file is not empty
                           if (!String.IsNullOrEmpty(text))
                           {
                               // Converts the text to lower case
                               text = text.ToLowerCSafe();

                               // Removes diacritics
                               text = TextHelper.RemoveDiacritics(text);

                               // Creates a new Lucene.Net search document for the current text file
                               SearchDocumentParameters documentParameters = new SearchDocumentParameters()
                               {
                                   Index = srchInfo,
                                   Type = SearchHelper.CUSTOM_SEARCH_INDEX,
                                   Id = Guid.NewGuid().ToString(),
                                   Created = fi.CreationTime
                               };
                               ILuceneSearchDocument doc = LuceneSearchDocumentHelper.ToLuceneSearchDocument(SearchHelper.CreateDocument(documentParameters));

                               // Adds a content field. This field is processed when the search looks for matching results.
                               doc.AddGeneralField(SearchFieldsConstants.CONTENT, text, SearchHelper.StoreContentField, true);

                               // Adds a title field. The value of this field is used for the search result title.
                               doc.AddGeneralField(SearchFieldsConstants.CUSTOM_TITLE, fi.Name, true, false);

                               // Adds a content field. The value of this field is used for the search result excerpt.
                               doc.AddGeneralField(SearchFieldsConstants.CUSTOM_CONTENT, TextHelper.LimitLength(text, 200), true, false);

                               // Adds a date field. The value of this field is used for the date in the search results.
                               doc.AddGeneralField(SearchFieldsConstants.CUSTOM_DATE, fi.CreationTime, true, false);

                               // Adds a url field. The value of this field is used for link urls in the search results.
                               doc.AddGeneralField(SearchFieldsConstants.CUSTOM_URL, file, true, false);

                               // Adds an image field. The value of this field is used for the images in the search results.
                               // Commented out, since the image file does not exist by default
                               // doc.AddGeneralField(SearchFieldsConstants.CUSTOM_IMAGEURL, "textfile.jpg", true, false);

                               // Adds the document to the index
                               iw.AddDocument(doc);
                           }
                       }

                       // Flushes the index buffer
                       iw.Flush();

                       // Optimizes the index
                       iw.Optimize();
                   }
               }

               // Logs any potential exceptions
               catch (Exception ex)
               {
                   Service.Resolve<IEventLogService>().LogException("CustomTextFileIndex", "Rebuild", ex);
               }

               // Always close the index writer
               finally
               {
                   iw.Close();
               }
           }
       }
   }

   ```
8. Rebuild the solution.

You need to write the code of the _Rebuild_ method according to the specific purpose of the index. Use the following general steps for all indexes:

1. Get an **Index writer** instance for the search index.
   - The index writer object must implement the **CMS.Search.IIndexWriter** interface.
   - Get the index writer by calling the _Provider.GetWriter(true)_ method of the _SearchIndexInfo_ object.
2. Define search **Documents** and their fields for the items that you wish to add to the index.
   - Custom search document objects must implement the **CMS.Search.ILuceneSearchDocument** interface.
   - Create search documents by calling the _CMS.Search.SearchHelper.CreateDocument_ method, and then convert the result via the _LuceneSearchDocumentHelper.ToLuceneSearchDocument_ method.
3. Call the **AddDocument** method of the _Index writer_ for every search document.
4. After you have added all required search documents, call the **Flush** and **Optimize** methods of the _Index writer_.
5. Call the **Close** method of the _Index writer_.

> **Info:** **Using data parameters for custom indexes**
>
> The **SearchIndexInfo** parameter of the _Rebuild_ method allows you to access the data fields of the corresponding search index object. The sample code loads the content of the **Index data** field and uses it to define the path to the searched text files.
>
> When writing your own custom indexes, you can use the _Index data_ field as a string parameter for any required purpose. The parameter allows you to modify the behavior of the index directly from the administration interface without having to edit the index code.

> **Info:** **Updating the content of custom indexes**
>
> By default, the only way to update the content of a custom index is to rebuild the whole index (i.e. using the implementation of the _Rebuild_ method). You cannot use the Xperience [search indexing tasks](https://docs.kentico.com/13/configuring-xperience/setting-up-search-on-your-website/monitoring-search-indexing-tasks.md) to update custom indexes.
>
> With additional custom development, you can update indexes by calling the **Update** or **Delete** methods of the **CMS.Search.SearchHelper** class. Typically, you need to update the index from custom code outside of the index class whenever the indexed content changes. However, in this case you need to ensure that the API is never called concurrently – problems can occur if multiple processes attempt to update the same index at the same time.

## Registering custom search indexes

1. Sign in to the Xperience administration interface and open the **Smart search** application.
2. Select the **Local indexes** tab.
3. Click **New Index**. Fill in the following properties:

   - **Display name**: Text file index
   - **Index type**: Custom Index
4. Click **Save**.
5. Switch to the **Indexed content** tab and enter the names of the assembly and class where the custom index is implemented:

   - **Assembly name**: CustomSearch _(the name of your custom assembly)_
   - **Class**: CustomSearch.TextFileIndex _(the name of the index class, including its namespace)_
6. Type any required parameters into the **Index data** field. In this example, you need to specify the file system path of the folder containing the text files that the index will search. You can create a new folder for this purpose, e.g. _C:\SearchExample\\_ and add some text files into it.
7. Click **Save**.
8. Go to the **General** tab and **Rebuild** the index.

## Result

The index is now fully functional. To test the index, switch to the **Search preview** tab and try searching for any words from the text files created in the _SearchExample_ folder.
