IL.UmbracoSearch
17.4.2.1
dotnet add package IL.UmbracoSearch --version 17.4.2.1
NuGet\Install-Package IL.UmbracoSearch -Version 17.4.2.1
<PackageReference Include="IL.UmbracoSearch" Version="17.4.2.1" />
<PackageVersion Include="IL.UmbracoSearch" Version="17.4.2.1" />
<PackageReference Include="IL.UmbracoSearch" />
paket add IL.UmbracoSearch --version 17.4.2.1
#r "nuget: IL.UmbracoSearch, 17.4.2.1"
#:package IL.UmbracoSearch@17.4.2.1
#addin nuget:?package=IL.UmbracoSearch&version=17.4.2.1
#tool nuget:?package=IL.UmbracoSearch&version=17.4.2.1
IL.UmbracoSearch
A comprehensive search solution for Umbraco, supporting both Lucene and Azure Search, with extensible indexing and flexible search parameters.
Table of Contents
- IL.UmbracoSearch
Features
- Full-text search: Search for keywords in the content of the documents.
- Filtering: Filter search results by various criteria, such as document type, date range, and tags.
- Sorting: Sort search results by relevance, date, or any other field.
- Faceting: Get a count of the number of documents that match each value of a field.
- Hybrid search (Azure only): Combine keyword search with vector search for more relevant results.
- Extensible: Add your own custom fields to the search index.
Quick Start
Here's the simplest way to get started.
- Install the package and configure your services and
appsettings.json(see Configuration). - Inject
ISearchServiceinto your controller or service. - Perform a search:
// Inject the search service
private readonly ISearchService _searchService;
public MyController(ISearchService searchService)
{
_searchService = searchService;
}
// Perform a basic search
public async Task<IActionResult> Search(string query)
{
var searchParameters = new SearchParameters
{
FullTextSearch = new FullTextSearch(query),
Take = 20
};
var results = await _searchService.SearchAsync<CommonSearchItemModel>(searchParameters);
// 'results' now contains the top 20 hits for the query.
// You can access properties like results.Items, results.Total, etc.
return View(results);
}
Configuration
This library attaches to existing Umbraco indexes (e.g., ExternalIndex, InternalIndex). It does not create new indexes.
1. Service Registration
In your Program.cs, register the services by calling AddServiceAttributeBasedDependencyInjection.
// Program.cs
builder.AddServiceAttributeBasedDependencyInjection(options =>
{
options.AddFeature(SearchOptions.Lucene);
// or options.AddFeature(SearchOptions.Azure);
});
2. AppSettings
Add a SearchSettings section to your appsettings.json.
<details>
<summary><strong>Example appsettings.json</strong></summary>
"SearchSettings": {
"LicenseToken": "YOUR_LICENSE_TOKEN",
"DefaultIndexName": "ExternalIndex",
"Indexes": [
"ExternalIndex",
"InternalIndex"
],
"ReadOnly": false,
"PreviewIndexes": ["InternalIndex"],
"CustomIndexSuffix": "Dev|Staging|DeveloperMachine|None",
"EnableBlueGreenIndexing": false,
"DisableSwapDelay": false,
"EnableTaxonomyFacetExpansion": false,
"Azure": {
"ServiceUrl": "YOUR_AZURE_SEARCH_SERVICE_URL",
"ApiKey": "YOUR_AZURE_SEARCH_API_KEY",
"UseHybridSearch": false
},
"OpenAi": {
"ApiKey": "YOUR_OPENAI_API_KEY",
"ServiceUrl": "YOUR_OPENAI_SERVICE_URL",
"EmbeddingsDeploymentName": "text-embedding-3-large"
}
}
</details>
Configuration Details
- LicenseToken: Your license token for the package.
- DefaultIndexName: The default index to use if not specified in a search.
- CustomIndexSuffix: Allows to specify custom suffix applied to all of your configured indexes. Optional.
- Indexes: A list of search index names to be used. Defaults to
["ExternalIndex"]. - PreviewIndexes: A list of search index names where soft deletion is enabled.
- Azure: Azure Search service credentials.
- OpenAi: OpenAI credentials for vector embeddings.
- ReadOnly: Disallows runtime to create or modify existing index in any way.
- EnableBlueGreenIndexing: Allows to enable b/g indexing behavior when rebuilding index, so that old functional index temporary remains available for search operations.
- DisableSwapDelay: System will delay index swap to allow for indexing to be finalized (1 min per 1000 items of delay); you can disable this behavior with this flag (probably for dev/test purpose).
- EnableTaxonomyFacetExpansion: Enables hierarchical taxonomy facet expansion for
TaxonomyTagsvalues. Keep disabled if you want to work with raw facet values only.
Basic Usage
To perform a search, inject ISearchService and call SearchAsync with a SearchParameters object.
var searchParameters = new SearchParameters
{
FullTextSearch = new FullTextSearch("umbraco", useHybridSearch: true),
Skip = 0,
Take = 10,
Aliases = new[] { "contentPage" },
SearchOrderings = new List<ISearchOrdering>
{
ISearchOrdering.ByScore(OrderingType.Descending),
ISearchOrdering.ByField(IndexingConstants.ComputedIndexFields.SearchDate, OrderingType.Descending)
},
Filters = new List<SearchFilterBase>
{
new SearchFilter
{
Fields = [IndexingConstants.ComputedIndexFields.NodeTypeAlias],
Values = new[] { "contentPage" },
FilteringBehavior = FilteringBehavior.And
}
},
FacetOn = new List<FacetOn>
{
new FacetOn(IndexingConstants.ComputedIndexFields.TaxonomyTags)
},
LanguageIsoCode = "en-US"
};
var searchResults = await searchService.SearchAsync<CommonSearchItemModel>(searchParameters);
Taxonomy Indexing and Faceting
The taxonomy helpers let you index a full taxonomy path once and then facet/filter on category levels separately from final tag values.
Indexing taxonomy values
Use AddTaxonomyValue during indexing. Each call accepts path segments and stores:
- full value in
TaxonomyTagsas"__"-joined path (for exampleCategory__SubCategory__Tag) - category-only path in
TaxonomyCategories(for exampleCategory__SubCategory)
indexingObject
.AddTaxonomyValue("Category", "SubCategory", "TagName")
.AddTaxonomyValue("Category", "SubCategory", "TagName2");
Enabling hierarchical facet expansion
Set the following appsetting to true:
"SearchSettings": {
"EnableTaxonomyFacetExpansion": true
}
When enabled, the decorator expands facets requested on TaxonomyTags into hierarchical levels.
Use TaxonomyCategories for category filters and TaxonomyTags for terminal tag filters.
var searchParameters = new SearchParameters
{
FacetOn = [new FacetOn(IndexingConstants.ComputedIndexFields.TaxonomyTags)],
Filters =
[
// Drill down by category path
ISearchFilter.FilterFor(IndexingConstants.ComputedIndexFields.TaxonomyCategories,
"Category__SubCategory")
]
};
SharedTagsis still available as an obsolete alias for backwards compatibility; preferTaxonomyTagsfor new code.
The SearchParameters Object
This object allows you to define your query:
- FullTextSearch: The full-text search query.
- Skip/Take: For pagination.
- Aliases: Filter by document type aliases.
- SearchOrderings: Define how to sort results.
- Filters: Apply filters to the search.
- FacetOn: Request facet counts for specific fields.
- ExtraBoostingOptions: Boost the score of certain documents.
- IndexName: Specify which index to search.
- LanguageIsoCode: The culture to search in.
Advanced Usage
Advanced SearchApiController Example
For a complete example of how to build a search query from URL parameters, see the controller below. It handles parsing filters, ordering, facets, and more from the query string.
An example URL for this controller would be:
/Search/Search?q=umbraco&filters=__NodeTypeAlias:contentPage&orderBy=score:desc
<details>
<summary><strong>SearchApiController Code</strong></summary>
public class SearchApiController(ISearchService searchService, IIndexService indexService) : Controller
{
[HttpGet]
public async Task<IActionResult> Search([FromQuery] string q = "",
[FromQuery] bool useHybridSearch = false,
[FromQuery] string? filters = null,
[FromQuery] string? orderBy = null,
[FromQuery] string? facetOn = null,
[FromQuery] string? boostById = null,
[FromQuery] int skip = 0,
[FromQuery] int take = int.MaxValue,
[FromQuery] int? root = null,
[FromQuery] string? indexName = null,
[FromQuery] string? lang = null
)
=>
new JsonResult(await searchService.SearchAsync<CommonSearchItemModel>(
new SearchParameters
{
FullTextSearch = new FullTextSearch(q, useHybridSearch: useHybridSearch)
{
VectorSimilarityThreshold = 0.3f,
},
Aliases = [ContentPage.ModelTypeAlias],
Filters = TryBuildFilters(filters, indexName),
FacetOn = TryBuildFacetOn(facetOn, indexName),
SearchOrderings = TryBuildSearchOrderings(orderBy, indexName),
ExtraBoostingOptions = TryBuildExtraBoostingOptions(boostById),
Skip = skip,
Take = take,
Root = root,
IndexName = indexName,
LanguageIsoCode = lang
}));
// Helper methods for parsing query string parameters...
}
</details>
Multi-Language Support
The package supports multi-language indexing and search. The system automatically creates language-specific fields (e.g., productName_en, productName_de).
Multi-language fields can be defined via factory methods using isMultiLanguage: true (for example IndexFieldDefinitionFactory.ForString(..., isMultiLanguage: true)).
Set the LanguageIsoCode property in SearchParameters to search in a specific language.
Customizing the Index
You can add your own custom fields to the search index by implementing the IIndexingConverter interface.
IndexFieldDefinition constructors are no longer part of the public API.
Always create field definitions through IndexFieldDefinitionFactory (ForString, ForStringArray, ForInt, ForBool, Custom or AutoDetection).
The IIndexingConverter Interface
A class implementing this interface allows you to:
GetIndexFieldDefinitions(): Return a collection ofIIndexFieldDefinitionobjects that define your custom fields.AddContentComputedFields(): Add computed values to your custom fields for each document being indexed.AddMediaComputedFields(): Do the same for media items.RunForIndexes(): Specify which indexes the converter should run for.
Defining Index Fields
Create field definitions with IndexFieldDefinitionFactory and group them in a static constants class.
public static class CustomIndexingConstants
{
public const string CustomFieldPrefix = "custom_";
// Full-text searchable + sortable string field
public static readonly IndexFieldDefinition<string> ProductName =
IndexFieldDefinitionFactory.ForString(
fieldName: $"{CustomFieldPrefix}productName",
isSearchable: true,
isFacetable: false,
isSortable: true,
isFilterable: true);
// Multi-value string field for filters/facets
public static readonly IndexFieldDefinition<string[]> ProductCategories =
IndexFieldDefinitionFactory.ForStringArray(
fieldName: $"{CustomFieldPrefix}productCategories",
isFacetable: true,
isFilterable: true);
// Vector field stored only in Azure (Lucene definition intentionally null)
public static readonly IndexFieldDefinition<float[]> ProductVector =
IndexFieldDefinitionFactory.Custom<float[]>(
luceneFieldDefinition: null,
azureFieldDefinition: new VectorSearchField(
name: $"{CustomFieldPrefix}productVector",
vectorSearchDimensions: 1536,
vectorSearchProfileName: "vector-profile"));
}
Auto-Discovery with [Service] Attribute
Decorate your IIndexingConverter implementation with the [Service] attribute to have it automatically registered by the dependency injection container.
Example: Custom Indexing Converter
[Service<SearchOptions>(Lifetime = ServiceLifetime.Singleton, Feature = SearchOptions.Lucene | SearchOptions.Azure)]
public class CustomIndexingConverter : IIndexingConverter
{
public int Order => 100; // Higher order runs after core converters
public IEnumerable<IIndexFieldDefinition> GetIndexFieldDefinitions()
{
return new IIndexFieldDefinition[]
{
CustomIndexingConstants.ProductName,
CustomIndexingConstants.ProductCategories
};
}
public void AddContentComputedFields(IPublishedContent? publishedContent, IndexingModel indexingObject)
{
if (publishedContent != null)
{
indexingObject.SetComputedValue(CustomIndexingConstants.ProductName, "My Product");
indexingObject.SetComputedValue(CustomIndexingConstants.ProductCategories, new[] { "Category1", "Category2" });
}
}
}
Customizing Search Results
CommonSearchItemModel
The default CommonSearchItemModel provides access to common fields like Id, NodeName, SearchTitle, SearchDescription, Url, etc.
Custom Models with ValueFor<T>
For custom data, create your own search result model by inheriting from SearchResultModelBase. Use the ValueFor<T>(IndexFieldDefinition<T> fieldDefinition) method to retrieve values from the index.
public class ProductSearchResultModel : SearchResultModelBase
{
public string ProductName => ValueFor(CustomIndexingConstants.ProductName) ?? string.Empty;
public string[] Categories => ValueFor(CustomIndexingConstants.ProductCategories) ?? Array.Empty<string>();
}
This approach provides strongly-typed properties for your custom fields while allowing dynamic access to any indexed field.
Built-in Indexing Constants
The library provides a set of pre-defined constants for common Umbraco fields in IndexingConstants.ComputedIndexFields. You can reuse these in your queries and converters.
<details>
<summary><strong>List of IndexingConstants.ComputedIndexFields</strong></summary>
public static class IndexingConstants
{
public static class ComputedIndexFields
{
public const string ComputedFieldNameCommonPrefix = "computed";
public static readonly IndexFieldDefinition<int> UmbracoNodeIdInt =
IndexFieldDefinitionFactory.ForInt("intNodeId", isFacetable: false);
public static readonly IndexFieldDefinition Url =
IndexFieldDefinitionFactory.ForString(
$"{ComputedFieldNameCommonPrefix}{nameof(Url)}",
isFacetable: false,
isSortable: true,
isRaw: true,
isMultiLanguage: true);
public static readonly IndexFieldDefinition<float[]> VectorSearchContent =
IndexFieldDefinitionFactory.Custom<float[]>(
luceneFieldDefinition: null,
azureFieldDefinition: new VectorSearchField(
$"{ComputedFieldNameCommonPrefix}{nameof(VectorSearchContent)}",
1536,
"vector-profile"));
}
}
</details>
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Azure.AI.OpenAI (>= 2.1.0)
- Azure.Search.Documents (>= 11.8.0-beta.1)
- IL.AttributeBasedDI (>= 2.7.1.1)
- IL.Licensing.Validation (>= 17.4.2.1)
- Microsoft.EntityFrameworkCore (>= 10.0.1)
- Microsoft.EntityFrameworkCore.InMemory (>= 10.0.1)
- Microsoft.EntityFrameworkCore.SqlServer (>= 10.0.1)
- Microsoft.ML.Tokenizers (>= 2.0.0)
- Microsoft.ML.Tokenizers.Data.Cl100kBase (>= 2.0.0)
- Umbraco.Cms.Examine.Lucene (>= 17.0.0 && < 18.0.0)
- Umbraco.Cms.Web.Common (>= 17.0.0 && < 18.0.0)
-
net8.0
- Azure.AI.OpenAI (>= 2.1.0)
- Azure.Search.Documents (>= 11.8.0-beta.1)
- IL.AttributeBasedDI (>= 2.7.1.1)
- IL.Licensing.Validation (>= 17.4.2.1)
- Microsoft.EntityFrameworkCore (>= 8.0.22)
- Microsoft.EntityFrameworkCore.InMemory (>= 8.0.22)
- Microsoft.EntityFrameworkCore.SqlServer (>= 8.0.22)
- Microsoft.ML.Tokenizers (>= 2.0.0)
- Microsoft.ML.Tokenizers.Data.Cl100kBase (>= 2.0.0)
- Umbraco.Cms.Examine.Lucene (>= 10.0.0 && < 14.0.0)
- Umbraco.Cms.Web.Common (>= 10.0.0 && < 14.0.0)
-
net9.0
- Azure.AI.OpenAI (>= 2.1.0)
- Azure.Search.Documents (>= 11.8.0-beta.1)
- IL.AttributeBasedDI (>= 2.7.1.1)
- IL.Licensing.Validation (>= 17.4.2.1)
- Microsoft.EntityFrameworkCore (>= 9.0.11)
- Microsoft.EntityFrameworkCore.InMemory (>= 9.0.11)
- Microsoft.EntityFrameworkCore.SqlServer (>= 9.0.11)
- Microsoft.ML.Tokenizers (>= 2.0.0)
- Microsoft.ML.Tokenizers.Data.Cl100kBase (>= 2.0.0)
- Umbraco.Cms.Examine.Lucene (>= 16.0.0 && < 17.0.0)
- Umbraco.Cms.Web.Common (>= 16.0.0 && < 17.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 17.4.2.1 | 29 | 3/15/2026 |
| 17.4.1.1 | 32 | 3/14/2026 |
| 17.4.0.2 | 31 | 3/14/2026 |
| 17.4.0.1 | 31 | 3/14/2026 |
| 17.3.1.2 | 27 | 3/14/2026 |
| 17.3.1.1 | 31 | 3/14/2026 |
| 17.3.0.2 | 37 | 3/12/2026 |
| 17.3.0.1 | 33 | 3/12/2026 |
| 17.3.0-preview3 | 33 | 3/12/2026 |
| 17.3.0-preview2 | 35 | 3/12/2026 |
| 17.3.0-preview1 | 29 | 3/11/2026 |
| 17.2.0.5 | 36 | 3/10/2026 |
| 17.2.0.4 | 30 | 3/10/2026 |
| 17.2.0.3 | 43 | 3/10/2026 |
| 17.2.0.2 | 48 | 3/8/2026 |
| 17.2.0.1 | 36 | 3/8/2026 |
| 17.2.0-preview3 | 38 | 3/8/2026 |
| 17.2.0-preview2 | 29 | 3/8/2026 |
| 17.2.0-preview1 | 41 | 3/8/2026 |
| 17.1.1.3 | 73 | 2/26/2026 |