Ekom.Algolia
0.2.63
dotnet add package Ekom.Algolia --version 0.2.63
NuGet\Install-Package Ekom.Algolia -Version 0.2.63
<PackageReference Include="Ekom.Algolia" Version="0.2.63" />
<PackageVersion Include="Ekom.Algolia" Version="0.2.63" />
<PackageReference Include="Ekom.Algolia" />
paket add Ekom.Algolia --version 0.2.63
#r "nuget: Ekom.Algolia, 0.2.63"
#:package Ekom.Algolia@0.2.63
#addin nuget:?package=Ekom.Algolia&version=0.2.63
#tool nuget:?package=Ekom.Algolia&version=0.2.63
Ekom.Algolia
Algolia integration plugin for Ekom (Umbraco).
Features
- Product indexing with background queue/worker.
- Category indexing with background queue/worker.
- Standard Umbraco content indexing with background queue/worker.
- Product search service with Algolia SDK
SearchForHitsrequests. - Category search service with Algolia SDK
SearchForHitsrequests. - Standard content search and federated multi-index search.
- Algolia Insights events for view, add-to-cart, checkout, purchase.
- In-memory search result caching with store-scoped invalidation after reindex/update/delete.
- Index naming convention:
{primary|replica|query_suggestions}.ENVIRONMENT.STORE.ENTITY[_sorted_by_{asc|desc}_ATTRIBUTE][.Locale][.Currency].
Install
Choose the NuGet package that matches the Ekom/Umbraco application:
| Package | Target | Ekom dependency |
|---|---|---|
Ekom.Algolia |
.NET 8 (Ekom.U10/Umbraco 13) or .NET 10 (Umbraco 17) | Ekom.U10 or Ekom.U17, selected by target framework |
Ekom.Algolia.U18 |
.NET 10 and Umbraco 18 | Ekom.U18 |
dotnet add package Ekom.Algolia
# Umbraco 18:
dotnet add package Ekom.Algolia.U18
Both packages expose the same configuration and services. Register services explicitly; the Umbraco composers register notifications and Ekom event handlers, not the service collection:
using Ekom.Algolia;
services.AddAlgolia();
Search products:
using Algolia.Search.Models.Search;
using Ekom.Algolia.Models.Search;
using Ekom.Algolia.Services;
public sealed class ProductSearchController
{
private readonly IAlgoliaSearchService _algoliaSearchService;
public ProductSearchController(IAlgoliaSearchService algoliaSearchService)
{
_algoliaSearchService = algoliaSearchService;
}
public async Task<IReadOnlyList<string>> SearchAsync(CancellationToken ct)
{
var response = await _algoliaSearchService.SearchProductsAsync(
new AlgoliaSearchRequest
{
StoreAlias = "Store",
Locale = "en-US",
Currency = "USD",
Query = new SearchForHits
{
Query = "shoe",
HitsPerPage = 20,
Filters = "Available:1"
}
},
ct).ConfigureAwait(false);
return response.Hits.Select(x => x.Title).ToList();
}
}
Configuration (appsettings.json)
{
"Ekom": {
"Algolia": {
"Enabled": true,
"ApplicationId": "APP_ID",
"AdminApiKey": "ADMIN_API_KEY",
"SearchApiKey": "SEARCH_API_KEY",
"InsightsApiKey": "INSIGHTS_API_KEY",
"AnalyticsRegion": "eu",
"TransformationRegion": "eu",
"Environment": "prod",
"Replacement": {
"MaxRetries": 800
},
"Transformation": {
"MaxBatchSize": 250,
"MaxAttempts": 3,
"RetryBaseDelayMilliseconds": 1000,
"EnableSdkLogging": false
},
"Indexing": {
"ProductCustomRanking": [
"desc(available)",
"desc(productRanking)",
"desc(categoryRanking)"
],
"CategoryCustomRanking": [
"asc(sortOrder)"
],
"Dispatching": {
"MaxBatchSize": 100,
"FlushIntervalSeconds": 2,
"MaxQueueSize": 10000,
"MaxConcurrency": 2
}
},
"ContentIndexing": {
"Enabled": true,
"EnforcePublisherOnly": true,
"BatchSize": 1000,
"Dispatching": {
"MaxBatchSize": 100,
"FlushIntervalSeconds": 2,
"MaxQueueSize": 10000,
"MaxConcurrency": 2
},
"OversizedRecords": {
"Behavior": "Fail",
"MaxSizeBytes": 100000
},
"Indexes": [
{
"IndexName": "SearchIndex",
"CustomRanking": [
"desc(updateDateUnixSecond)"
],
"ContentTypes": [
{
"Alias": "article",
"Properties": [
"title",
"summary|striphtml",
"publishedAt|unix"
]
}
]
}
]
},
"Search": {
"Enabled": true,
"Products": true,
"Categories": true,
"GroupVariantsByProduct": true,
"QuerySuggestions": true,
"IncludeUserToken": true,
"VaryCacheByUserToken": false,
"MinimumQueryLength": 2,
"MaxHitsPerPage": 100,
"QuerySuggestionsProvisioning": {
"Enabled": true,
"UseReplicas": false,
"MinimumHits": 5,
"MinimumLetters": 4,
"EnablePersonalization": false,
"AllowSpecialCharacters": false,
"Exclude": []
},
"Cache": {
"Enabled": true,
"DurationMinutes": 60,
"CacheEmptyResults": true
}
},
"Events": {
"Enabled": true,
"ViewedProduct": true,
"AddedToCart": true,
"StartedCheckout": true,
"Purchase": true
},
"Stores": [
{
"Alias": "Store",
"Indexing": {
"Enabled": true,
"Products": true,
"Categories": true,
"Variants": false,
"BatchSize": 1000,
"ProductCustomRanking": [
"desc(productRanking)"
],
"CategoryCustomRanking": [
"asc(sortOrder)"
],
"ProductProperties": [
"channels|array",
"packageCount|int",
"weight|decimal",
"publishedAt|unix",
"description|striphtml"
],
"SortedReplicas": [
{
"Attribute": "price",
"Direction": "Asc"
},
{
"Attribute": "createdDateInDKUnix",
"Direction": "Desc",
"Type": "Standard"
}
]
},
"SearchableAttributes": [
"title",
"sku",
"unordered(summary)"
],
"Collections": {
"Enabled": true
},
"LanguageSettings": {
"QueryLanguages": ["en"],
"IndexLanguages": ["en"],
"RemoveStopWords": true,
"IgnorePlurals": true,
"IgnorePluralsLanguages": ["en"]
}
}
]
}
}
}
Settings reference
| Setting | Type | Default | Description |
|---|---|---|---|
Enabled |
bool |
true |
Enables the Algolia plugin. |
ApplicationId |
string |
required | Algolia application ID. |
AdminApiKey |
string |
required | API key used for indexing, settings, replicas, and query suggestion provisioning. |
SearchApiKey |
string |
required | API key used by IAlgoliaSearchService search requests. |
InsightsApiKey |
string |
null |
Optional key for Insights events. Falls back to AdminApiKey when omitted. |
AnalyticsRegion |
string |
null |
Algolia analytics region for query suggestions, usually us or eu. If omitted, the plugin tries both. |
TransformationRegion |
string |
eu |
Algolia transformation region used by collection-enabled stores. Must be eu or us. |
Environment |
string |
prod |
Environment segment used in generated index names. |
Replacement:MaxRetries |
int |
800 |
Maximum number of Algolia task-status polling retries while atomically replacing an existing index. This doesn't retry failed uploads. |
Transformation:MaxBatchSize |
int |
250 |
Maximum records per collection transformation batch. Smaller configured indexing batches remain unchanged. |
Transformation:MaxAttempts |
int |
3 |
Maximum attempts for collection transformation writes that fail with a transient transport error. |
Transformation:RetryBaseDelayMilliseconds |
int |
1000 |
Initial delay before retrying a transient transformation failure. Later retries use exponential backoff capped at 30 seconds. |
Transformation:EnableSdkLogging |
bool |
false |
Enables all logging from the shared Algolia SDK client, including high-volume HTTP request and task-polling logs. Ekom lifecycle and error logs remain enabled when this is false. |
Indexing:Enabled |
bool |
true |
Enables indexing features for stores without a store-level Indexing section. |
Indexing:Products |
bool |
true |
Enables product indexing. |
Indexing:Categories |
bool |
true |
Enables category indexing. |
Indexing:Variants |
bool |
false |
Indexes product variants as separate product records so variant SKUs can be searched directly. |
Indexing:BatchSize |
int |
1000 |
Batch size for Algolia save/replace/delete operations. |
Indexing:ProductProperties |
string[] |
[] |
Additional product properties/metafields to include in product records. Supports modifiers documented below. |
Indexing:ProductCustomRanking |
string[] |
[] |
Ordered Algolia custom-ranking expressions applied to primary product indexes, such as desc(productRanking). Omit or leave empty to preserve the setting managed in Algolia. |
Indexing:CategoryCustomRanking |
string[] |
[] |
Ordered Algolia custom-ranking expressions applied to primary category indexes, such as asc(sortOrder). Omit or leave empty to preserve the setting managed in Algolia. |
Indexing:AttributesForFaceting |
string[] |
[] |
Algolia facet expressions to preserve on product indexes, such as filterOnly(categoryPageId) or searchable(brand). |
Indexing:FacetAttributes |
string[] |
[] |
Additional product properties/metafields to include under attributes and configure as facets; filterable metafields are included automatically. |
Indexing:VariantFacetAttributes |
object |
{} |
Maps facet output names to variant: or variantGroup: property sources. |
Indexing:SortedReplicas |
object[] |
[] |
Sorted replica definitions. Each entry uses Attribute, Direction (Asc or Desc), and optional Type (Virtual or Standard). Type defaults to Virtual. |
Indexing:Dispatching:MaxBatchSize |
int |
100 |
Maximum queued jobs processed in one worker batch. |
Indexing:Dispatching:FlushIntervalSeconds |
int |
2 |
Worker delay between queue flushes. |
Indexing:Dispatching:MaxQueueSize |
int |
10000 |
Maximum in-memory queue size. |
Indexing:Dispatching:MaxConcurrency |
int |
2 |
Maximum indexing worker concurrency. |
ContentIndexing:Enabled |
bool |
false |
Enables standard Umbraco content indexing. |
ContentIndexing:EnforcePublisherOnly |
bool |
true |
Skips notification-driven standard content indexing on Umbraco subscriber and unknown server roles. |
ContentIndexing:BatchSize |
int |
1000 |
Batch size for content index rebuild operations. |
ContentIndexing:Dispatching:MaxBatchSize |
int |
100 |
Maximum queued content jobs processed in one worker batch. |
ContentIndexing:Dispatching:FlushIntervalSeconds |
int |
2 |
Content worker delay between queue flushes. Values below one are treated as one second. |
ContentIndexing:Dispatching:MaxQueueSize |
int |
10000 |
Maximum content indexing queue size. Values at or below zero fall back to 10000. |
ContentIndexing:Dispatching:MaxConcurrency |
int |
2 |
Present in the shared dispatcher options but not currently used by the single-reader content worker. |
ContentIndexing:OversizedRecords:Behavior |
Fail or Skip |
Fail |
Fails content indexing or skips records that exceed the configured size limit. |
ContentIndexing:OversizedRecords:MaxSizeBytes |
int |
100000 |
Maximum serialized UTF-8 size of one content record. |
ContentIndexing:Indexes |
object[] |
[] |
Content indexes to maintain. Index names resolve as {IndexName}.{Environment}.{Culture}. |
ContentIndexing:Indexes[*]:CustomRanking |
string[] |
[] |
Ordered Algolia custom-ranking expressions applied to each resolved primary content index. Omit or leave empty to preserve the setting managed in Algolia. |
ContentIndexing:Indexes[*]:ContentTypes[*]:Alias |
string |
required | Umbraco content type alias to include in the content index. |
ContentIndexing:Indexes[*]:ContentTypes[*]:Properties |
string[] |
[] |
Property aliases to index. Use |unix or |unixms for numeric dates, or |striphtml for searchable plain text from rich-text values. |
Search:Enabled |
bool |
true |
Enables Algolia search services. |
Search:Products |
bool |
true |
Enables product search. |
Search:Categories |
bool |
true |
Enables category search. |
Search:GroupVariantsByProduct |
bool |
true |
When variant indexing is enabled, applies Algolia distinct so product searches group variant records by product. |
Search:QuerySuggestions |
bool |
false |
Enables query suggestion search and provisioning. |
Search:IncludeUserToken |
bool |
true |
Adds userToken to Algolia search requests using IAlgoliaUserTokenProvider, unless the query already has a token. |
Search:VaryCacheByUserToken |
bool |
false |
Includes userToken in search cache keys. Keep false for shared cache; set true when Algolia personalization changes result order/content per user. |
Search:MinimumQueryLength |
int |
2 |
Minimum query length before search executes. Set 0 to disable this guard. |
Search:MaxHitsPerPage |
int |
100 |
Upper bound for requested HitsPerPage. Set 0 or less to avoid clamping. |
Search:Cache:Enabled |
bool |
true |
Enables in-memory search response caching. |
Search:Cache:DurationMinutes |
int |
60 |
Search cache duration. |
Search:Cache:CacheEmptyResults |
bool |
true |
Whether empty result sets are cached. |
Search:QuerySuggestionsProvisioning:Enabled |
bool |
true |
Creates/updates Algolia query suggestion configuration automatically. |
Search:QuerySuggestionsProvisioning:UseReplicas |
bool |
false |
Includes source index replicas in query suggestion generation. |
Search:QuerySuggestionsProvisioning:MinimumHits |
int |
5 |
Minimum hits required for query suggestions. |
Search:QuerySuggestionsProvisioning:MinimumLetters |
int |
4 |
Minimum letters required for query suggestions. |
Search:QuerySuggestionsProvisioning:EnablePersonalization |
bool |
false |
Enables personalization for query suggestions configuration. |
Search:QuerySuggestionsProvisioning:AllowSpecialCharacters |
bool |
false |
Allows special characters in query suggestions. |
Search:QuerySuggestionsProvisioning:Exclude |
string[] |
[] |
Query suggestion exclusion list. |
Events:Enabled |
bool |
true |
Enables Algolia Insights events. |
Events:ViewedProduct |
bool |
true |
Sends product view events. |
Events:AddedToCart |
bool |
true |
Sends add-to-cart conversion events. |
Events:StartedCheckout |
bool |
true |
Sends checkout conversion events. |
Events:Purchase |
bool |
true |
Sends purchase conversion events. |
Stores |
object[] |
[] |
Store aliases supported by the plugin. Locale/currency are resolved from Ekom store data. |
Stores[*]:Alias |
string |
required | Ekom store alias. |
Stores[*]:Indexing |
object |
null |
Complete indexing configuration for this store. When present, it replaces the global Indexing section except for global Dispatching. |
Stores[*]:Indexing:* |
same as Indexing:* |
same as global defaults | Supports Enabled, Products, Categories, Variants, BatchSize, product/facet fields, custom ranking, and sorted replicas. Omitted members use defaults rather than individual global values. |
Stores[*]:IncludeStock |
bool |
false |
Includes product stock in indexed records for this store. |
Stores[*]:EnableAvailabilityUpdates |
bool |
false |
Partially updates indexed availability after stock changes for this store. When IncludeStock is enabled, also updates the indexed stock amount. |
Stores[*]:SearchableAttributes |
string[] |
[] |
Ordered searchable attributes for this store's product indexes and standard replicas. When omitted or empty, the plugin preserves the setting managed in Algolia. |
Stores[*]:Collections:Enabled |
bool |
false |
Routes product writes through the Algolia transformation pipeline for this store. |
Stores[*]:LanguageSettings:QueryLanguages |
string[] |
[] |
ISO 639-1 languages used for language-specific query processing. |
Stores[*]:LanguageSettings:IndexLanguages |
string[] |
[] |
ISO 639-1 languages used for language-specific indexing. |
Stores[*]:LanguageSettings:RemoveStopWords |
bool |
null |
Enables or disables stop-word removal for this store's product indexes. |
Stores[*]:LanguageSettings:IgnorePlurals |
bool |
null |
Enables or disables matching singular, plural, and inflected forms for this store's product indexes. |
Stores[*]:LanguageSettings:IgnorePluralsLanguages |
string[] |
[] |
Limits plural handling to these ISO 639-1 languages when IgnorePlurals is true. |
Usage notes
Per-store indexing
The top-level Indexing section remains supported and is the fallback for every store without its own Indexing section. Add a complete Indexing section to a store when its product data, facets, variants, replicas, or batch size differ:
{
"Indexing": {
"Enabled": false,
"Dispatching": {
"MaxBatchSize": 100,
"FlushIntervalSeconds": 2,
"MaxQueueSize": 10000,
"MaxConcurrency": 2
}
},
"Stores": [
{
"Alias": "StoreA",
"Indexing": {
"Enabled": true,
"Products": true,
"Categories": true,
"Variants": true,
"BatchSize": 500,
"ProductProperties": ["brand", "description|striphtml"],
"FacetAttributes": ["brand"],
"SortedReplicas": [
{ "Attribute": "price", "Direction": "Asc" }
]
}
},
{
"Alias": "StoreB"
}
]
}
StoreA uses its complete local section and can enable indexing even when global Indexing:Enabled is false. StoreB uses the global section. Store-level collections, dictionaries, and custom-ranking lists replace global values. Dispatching is always global because all stores share the same queues and workers. Ekom:Algolia:Enabled remains the master switch for the entire plugin. Rebuild a store after changing settings that alter its record or index schema.
Custom ranking
Configure Algolia custom-ranking expressions in priority order. Product settings apply to every primary locale/currency index generated from the corresponding global or per-store indexing configuration. Category settings apply to each locale-specific category index. Content settings apply to every culture-specific index resolved from that content-index entry.
{
"Indexing": {
"ProductCustomRanking": [
"desc(available)",
"desc(productRanking)",
"desc(categoryRanking)"
],
"CategoryCustomRanking": [
"asc(sortOrder)"
]
},
"ContentIndexing": {
"Indexes": [
{
"IndexName": "SearchIndex",
"CustomRanking": [
"desc(updateDateUnixSecond)"
]
}
]
}
}
Expressions use Algolia's asc(attribute) or desc(attribute) syntax, and attribute names are case-sensitive. The plugin trims entries, removes blank values and duplicates, and preserves order. If a property is omitted, empty, or contains only whitespace, the existing Algolia setting remains unmanaged. Settings are sent only to primary indexes and aren't forwarded to replicas.
Algolia Collections
Enable Collections only for stores whose product indexes are connected to an Algolia Collection. Product rebuilds, incremental writes, and stock-driven updates for those stores are sent through Algolia's transformation pipeline so Algolia can maintain the managed _collections attribute.
{
"TransformationRegion": "eu",
"Stores": [
{
"Alias": "Store",
"Collections": {
"Enabled": true
}
}
]
}
The transformation region must be eu or us. Configure no other Push connectors for the same collection indexes. The plugin adds _collections to attributesForFaceting but doesn't map or overwrite the attribute in product records. Collection-enabled writes are capped at Transformation:MaxBatchSize and transient transport failures are retried according to the transformation settings.
If Algolia reports that no Collections task exists for the exact index, the plugin logs a warning and uses the Search API for that operation. This allows the first index rebuild to complete before any collection has been created. Once Algolia creates the Collections task, later writes automatically use the Collections pipeline. Other transformation errors still fail without falling back, and the warning should be investigated if Collections already exist for the index.
Algolia SDK logging is disabled by default to avoid emitting every task-polling request during long rebuilds. Set Transformation:EnableSdkLogging to true temporarily when diagnosing SDK behavior; this enables all logging for the shared SDK client and can be high-volume. Ekom logs each exact index when a rebuild or update starts, completes, fails, or is submitted without waiting for publication.
Per-store searchable attributes
Configure searchable attributes independently for each store. The list applies to every locale/currency product index generated for that store and to its standard replicas. Virtual replicas inherit searchable attributes from their primary index.
"Stores": [
{
"Alias": "StoreA",
"SearchableAttributes": ["title", "sku", "unordered(summary)"]
},
{
"Alias": "StoreB",
"SearchableAttributes": ["title", "attributes.brand"]
}
]
Attribute names are case-sensitive, and built-in product properties are serialized with camel-case names such as title, sku, and summary. Algolia syntax such as unordered(description) and comma-grouped attributes is supported. Their order controls priority. If the list is omitted, empty, or contains only whitespace, the plugin doesn't send searchableAttributes, so values configured in the Algolia UI remain unchanged.
Sorted replicas
Sorted replicas default to Virtual, which uses Algolia relevant sorting without duplicating records. Set Type to Standard when exhaustive sorting is required. Virtual replicas use the configured attribute as custom ranking, while standard replicas place it first in the ranking formula.
"SortedReplicas": [
{ "Attribute": "price", "Direction": "Asc" },
{ "Attribute": "price", "Direction": "Desc", "Type": "Standard" }
]
Changing Type doesn't change the generated replica index name. Existing entries without Type are provisioned as virtual replicas after upgrading; set Type to Standard to retain exhaustive sorting. Virtual replicas require an Algolia plan that supports relevant sorting.
Insights user-token correlation with InstantSearch
Ekom issues an opaque first-party user-token cookie the first time IAlgoliaUserTokenProvider.GetOrCreateUserToken() is called. Use that same value to initialize the browser's Search Insights client so frontend click events and server-side conversion events share one Algolia user token. The token is not derived from a username or other personally identifiable information.
Resolve the token while rendering the Razor page and initialize Search Insights with it. Do not let Search Insights generate a competing user token.
@using Ekom.Algolia.Services
@inject IAlgoliaUserTokenProvider AlgoliaUserTokenProvider
@{
var algoliaUserToken = AlgoliaUserTokenProvider.GetOrCreateUserToken();
}
<script>
aa('init', {
appId: '@algoliaOptions.ApplicationId',
apiKey: '@algoliaOptions.SearchApiKey',
useCookie: false,
});
aa('setUserToken', '@algoliaUserToken');
</script>
When a result is added to an Ekom order, include that result's Algolia queryID as algoliaQueryId in the add-to-order request. Ekom persists the first query ID received for each order line along with the shared user token in OrderInfo.Tracking.Algolia.
await fetch('/ekom/order/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
productId,
quantity: 1,
storeAlias,
algoliaQueryId: searchResult.queryID,
}),
});
The stored token is used for Ekom's add-to-cart, checkout, and purchase Insights events. Add-to-cart and purchase conversions use Algolia's addToCart and purchase event subtypes so they are classified as ecommerce events. Ekom sends the persisted query ID with its matching order-line object data, together with VAT-inclusive unit price, optional unit discount, quantity, and the order currency. Checkout and purchase events group up to 20 order lines per Algolia event. The Order Info tracking view shows an Algolia subsection only when this data exists.
Stock availability updates
Enable partial product-record updates when stock changes:
{
"Ekom": {
"Algolia": {
"Stores": [
{
"Alias": "Store",
"EnableAvailabilityUpdates": true
}
]
}
}
}
Ekom updates Available only for stores with Stores[*]:EnableAvailabilityUpdates enabled, and only when effective sellable availability changes, including stock-buffer, backorder, and variant availability rules. For products with variants, the parent record becomes unavailable only when every variant is unavailable. If Stores[*]:IncludeStock is true, every stock change also partially updates Stock; indexed variant records additionally update variantStock. Existing records are updated only—stock changes never create incomplete Algolia records.
Filter indexed products
Register an IAlgoliaProductIndexFilter in the consuming solution to decide which products are indexed. Filters run after Ekom retrieves a product and before it maps the product for each resolved store, locale, and currency target. All registered filters must return true for the product to be indexed. Register filters as singletons because the indexing executor is a singleton.
using Ekom.Algolia.Mappers;
using Ekom.Models;
public sealed class HiddenProductAlgoliaFilter : IAlgoliaProductIndexFilter
{
public bool ShouldIndex(IProduct product, AlgoliaResolvedStore store)
=> product.Available;
}
services.AddSingleton<IAlgoliaProductIndexFilter, HiddenProductAlgoliaFilter>();
Filters apply to full rebuilds and incremental product indexing. When a filter excludes a product during an incremental update, Ekom leaves any existing Algolia record unchanged; it is removed on the next full rebuild.
Mapping extension points
The public mapper contracts under Ekom.Algolia.Mappers support project-specific records without changing the plugin:
IAlgoliaProductEnricher,IAlgoliaCategoryEnricher, andIAlgoliaContentEnrichermodify mapped records. Multiple implementations run in ascendingOrder.IAlgoliaProductFieldConverterconverts configured product properties andIAlgoliaContentPropertyValueConverterconverts configured Umbraco properties. Converters run in ascendingOrder; each converter whoseCanHandlereturnstruereceives the current value.IAlgoliaProductIndexFilterexcludes products before mapping, as shown above.IAlgoliaProductIndexMapperandIAlgoliaCategoryIndexMapperare the public mapper abstractions.AddAlgoliaregisters the built-in implementations, which can be replaced in DI when the complete mapping must change.
Register enrichers, converters, and filters as singletons because the indexing executors and built-in mappers are singletons:
using Ekom.Algolia.Mappers;
services.AddAlgolia();
services.AddSingleton<IAlgoliaProductEnricher, ProductSearchEnricher>();
services.AddSingleton<IAlgoliaCategoryEnricher, CategorySearchEnricher>();
services.AddSingleton<IAlgoliaContentEnricher, ContentSearchEnricher>();
services.AddSingleton<IAlgoliaProductFieldConverter, ProductFieldConverter>();
services.AddSingleton<IAlgoliaContentPropertyValueConverter, ContentPropertyConverter>();
Indexing triggers and API keys
Product and category indexing is triggered from Umbraco content notifications for ekmProduct and ekmCategory.
To avoid incremental indexing during a bulk import or a batch of direct IContentService changes, wrap the work in an AlgoliaIndexingScope. The scope suppresses notification-driven indexing and stock availability updates only for its current asynchronous execution flow. Dispose it before starting a full rebuild.
using Ekom.Algolia.Indexing;
using (AlgoliaIndexingScope.Suppress())
{
importService.FullSync(data);
}
fullIndexRebuildCoordinator.TryStart();
Search requests use SearchApiKey. Indexing, settings updates, replicas, delete operations, and query suggestion provisioning use AdminApiKey.
{
"Ekom": {
"Algolia": {
"ApplicationId": "APP_ID",
"AdminApiKey": "ADMIN_API_KEY",
"SearchApiKey": "SEARCH_API_KEY"
}
}
}
When Search:QuerySuggestions is enabled, the plugin provisions the separate query_suggestions... index configuration automatically. Set AnalyticsRegion to us or eu if you know it; if omitted, the plugin tries us and then eu.
{
"Ekom": {
"Algolia": {
"AnalyticsRegion": "eu",
"Search": {
"QuerySuggestions": true
}
}
}
}
Per-store language settings
Configure Algolia's language processing separately for each store. These settings are applied to the store's primary product indexes and sorted replicas. Virtual replicas inherit index-time settings from the primary because Algolia doesn't allow IndexLanguages to be changed directly on them. Algolia recommends configuring both QueryLanguages and IndexLanguages so query-time and index-time processing are consistent.
{
"Ekom": {
"Algolia": {
"Stores": [
{
"Alias": "IcelandicStore",
"LanguageSettings": {
"QueryLanguages": ["is"],
"IndexLanguages": ["is"],
"RemoveStopWords": false,
"IgnorePlurals": true,
"IgnorePluralsLanguages": ["is"]
}
},
{
"Alias": "EnglishStore",
"LanguageSettings": {
"QueryLanguages": ["en"],
"IndexLanguages": ["en"],
"RemoveStopWords": true,
"IgnorePlurals": true,
"IgnorePluralsLanguages": ["en"]
}
}
]
}
}
}
Language values are validated against Algolia's supported ISO 639-1 language codes. When IgnorePlurals is true, a non-empty IgnorePluralsLanguages collection limits plural handling to those languages; without that collection, Algolia applies the boolean setting normally. Omitted boolean settings preserve Algolia's defaults, even when plural languages are configured. Explicitly setting IgnorePlurals to false disables plural handling while retaining the configured language list. These settings don't apply to standard content or query-suggestions indexes.
Oversized content records
Standard content records are measured before they are sent to Algolia. The default ContentIndexing:OversizedRecords:Behavior is Fail, which stops the operation and logs enough Umbraco context to identify the node. Set it to Skip to exclude oversized records and continue indexing:
{
"Ekom": {
"Algolia": {
"ContentIndexing": {
"OversizedRecords": {
"Behavior": "Skip",
"MaxSizeBytes": 100000
}
}
}
}
}
Diagnostics include the index name, record size, NodeId, objectID, node name, content type alias, URL, and the five largest field names with their approximate byte sizes. Field values are not logged. When Skip is used during incremental indexing, any previous record for that node is deleted from Algolia to prevent stale content.
Algolia content record is too large for index SearchIndex.prod.en-US: size 102824/100000 bytes, NodeId 1234, ObjectID f0446822-c9cd-4bd9-8351-2e582153ce43, Name Example article, ContentTypeAlias article, Url /articles/example/, LargestFields body=101542, summary=640. Behavior: Fail.
Index naming and store context
The plugin resolves Algolia index names from the configured environment, store alias, locale, and currency. Callers should not set SearchForHits.IndexName; the search service sets it before executing the request.
Product index names include currency when a currency is resolved:
primary.prod.Store.products.en-US.USD
Category index names omit currency because category records are scoped by store alias and locale only:
primary.prod.Store.categories.en-US
Standard content index names resolve as {IndexName}.{Environment}.{Culture}:
SearchIndex.prod.en-US
Only the store alias must be configured in appsettings.json. Locale and currency are resolved from the Ekom store and the current request/order context. Background indexing falls back to the store's default culture and currency.
{
"Ekom": {
"Algolia": {
"Stores": [
{
"Alias": "Store"
}
]
}
}
}
Searching
IAlgoliaSearchService.SearchProductsAsync(...) returns typed product hits with paging metadata, query text, processing time, and raw facets.
var response = await algoliaSearchService.SearchProductsAsync(
new AlgoliaSearchRequest
{
StoreAlias = "Store",
Locale = "en-US",
Currency = "USD",
Query = new SearchForHits
{
Query = "shoe",
HitsPerPage = 20,
Filters = "Available:1"
}
},
ct).ConfigureAwait(false);
Other search methods target their own index types:
SearchCategoriesAsync(...)searches category records scoped by store alias and locale.SearchContentAsync(...)searches configured standard content indexes.FederatedSearchAsync(...)executes products, categories, query suggestions, and content searches in one Algolia multi-search request.
Search caching and user tokens
Search cache keys include the resolved index name and serialized Algolia query payload, so SDK options such as filters, facets, page, and hits-per-page affect caching.
Search:IncludeUserToken sends user context to Algolia. The default provider reads or creates an opaque random value in the first-party ekom_algolia_user_token cookie; it does not derive the token from a username or other personally identifiable information.
Search:VaryCacheByUserToken controls whether that token also affects cache keys. Leave it false for shared cache when results are not personalized. Set it to true when Algolia personalization changes result order or content per user.
{
"Ekom": {
"Algolia": {
"Search": {
"IncludeUserToken": true,
"VaryCacheByUserToken": false,
"Cache": {
"Enabled": true,
"DurationMinutes": 60
}
}
}
}
}
Product records and ranking fields
Product records provide the following fields without requiring any Indexing:ProductProperties configuration:
- Identity:
objectID,Sku,ProductId, andIsVariant. - Content:
NodeName,Title,Summary,Description, andUrl. - Images:
image_urlandImageUrls. - Pricing:
Price,PriceWithVat,PriceWithoutVat,PriceFormatted,PriceWithVatFormatted,PriceWithoutVatFormatted,Discounted,OriginalPrice,OriginalPriceFormatted, andCurrency. - Availability and ranking:
Available,ProductRanking, andCategoryRanking. - Store context and dates:
StoreAlias,Locale,CreatedAt, andUpdatedAt. - Categories:
categoryPageId,hierarchical_categories.lvl0, additional hierarchy levels when present, andcategory_paths.
Optional fields are omitted when no value is available. Stock is included only when Stores[*]:IncludeStock is enabled. Variant-specific fields are included when variant indexing is enabled, as described below.
PriceFormatted and PriceWithVatFormatted use Ekom's currency formatting for the VAT-inclusive indexed price. PriceWithoutVatFormatted formats the VAT-exclusive indexed price. Discounted is true when Ekom has a discount configured for the resolved price. OriginalPrice and OriginalPriceFormatted represent the resolved price before discount using the store's configured display VAT basis.
Title is a required top-level field. NodeName contains the Umbraco node name.
Available is indexed as a numeric value so it can be used for ranking:
{
"Title": "Running shoe",
"NodeName": "Running shoe - black",
"Available": 1
}
Product records also include ProductRanking and CategoryRanking integer fields. Both support negative values.
ProductRankingreads the productekmAlgoliaRankproperty and defaults to0when missing or invalid.CategoryRankinguses the highest validekmAlgoliaRankvalue across the product's categories and defaults to0when no category has a valid rank.
{
"ProductRanking": 10,
"CategoryRanking": 5
}
Category pages
categoryPageId contains the GUIDs of every category assigned to the product and all their ancestors. Values are deduplicated while preserving root-to-leaf order. This lets a category page filter by its own GUID and include products assigned to descendant categories. Variant records inherit the same identifiers.
{
"categoryPageId": [
"0f0ea901-6280-4586-b7fe-7ae731ed9489",
"4925fa5f-163a-4882-80af-b12e6501c30c",
"acee53b7-c6f6-4cb2-b940-508f1b49208d"
]
}
Configure categoryPageId as a persistent top-level filter-only facet, then apply the category GUID as a fixed InstantSearch filter:
{
"Ekom": {
"Algolia": {
"Indexing": {
"AttributesForFaceting": [
"filterOnly(categoryPageId)"
]
}
}
}
}
The plugin reapplies configured facet expressions during product index settings updates and rebuilds. When variant indexing is enabled, it also adds filterOnly(ProductId) and filterOnly(categoryPageId) automatically.
<Configure
filters={`categoryPageId:"${categoryKey}"`}
analyticsTags={['category-page']}
/>
Category hierarchy changes are not propagated to product records automatically. After moving, deleting, publishing, or changing a category hierarchy, queue a product-index rebuild with IAlgoliaProductIndexService.RebuildStoreAsync(storeAlias) or RebuildAllAsync(). The existing product indexing worker performs the rebuild in the background.
Variant indexing
Variants are not indexed by default. Enable variant indexing when products use placeholder parent SKUs and the real sellable SKUs live on variants.
{
"Ekom": {
"Algolia": {
"Indexing": {
"Variants": true
},
"Search": {
"GroupVariantsByProduct": true
}
}
}
}
When enabled, the plugin creates one additional product record per variant. Variant records use the variant SKU as top-level Sku, preserve the parent product SKU as ParentSku, and include ProductId and VariantId for grouping and selection.
{
"objectID": "product-key_variant-key",
"ProductId": "product-key",
"VariantId": "variant-key",
"Sku": "REAL-VARIANT-SKU",
"ParentSku": "PLACEHOLDER-SKU",
"IsVariant": true,
"variantSku": "REAL-VARIANT-SKU",
"variantTitle": "Black / XL"
}
Product indexes are configured with AttributeForDistinct = ProductId. Search:GroupVariantsByProduct defaults to true, so normal searches return grouped product results while SKU searches can still match variant records. Set it to false if you want one hit per matching variant.
{
"Ekom": {
"Algolia": {
"Search": {
"GroupVariantsByProduct": false
}
}
}
}
Additional product properties and metafields
Indexing:ProductProperties adds extra product properties and metafields that are not part of the default product record fields listed above. The built-in summary and description aliases may also be configured with |striphtml to transform their top-level record fields. Each entry supports one optional modifier: |array, |int, |decimal, |unix, |unixms, or |striphtml.
{
"Ekom": {
"Algolia": {
"Indexing": {
"ProductProperties": [
"channels|array",
"packageCount|int",
"weight|decimal",
"publishedAt|unix",
"description|striphtml"
]
}
}
}
}
Metafields can be indexed explicitly with metafield:<alias>:
{
"Ekom": {
"Algolia": {
"Indexing": {
"ProductProperties": [
"metafield:material",
"metafield:color",
"metafield:releaseDate|unix",
"metafield:longDescription|striphtml"
]
}
}
}
}
Modifier behavior:
|arrayparses JSON arrays such as["Web","Store"]into Algolia string arrays.|decimalaccepts comma or dot decimal separators, such as0,1and0.0.|striphtmlconverts direct HTML or rich-text JSON with amarkupproperty to plain text. It removes script and style content, decodes HTML entities, and normalizes tags and whitespace to single spaces.- Metafields with
Enable Multiple Choiceenabled are automatically indexed as string arrays. The|arraymodifier can still be used to explicitly index other metafields as arrays. - Invalid
|array,|int, and|decimalvalues are skipped instead of being indexed as strings. - Only one modifier is supported for each configured field.
Facet attributes
Indexing:AttributesForFaceting accepts raw Algolia facet expressions for existing top-level record fields. Entries may be plain attribute names or use Algolia modifiers such as filterOnly(...), searchable(...), and afterDistinct(...). Configured entries are trimmed, deduplicated case-insensitively, and merged with facets generated by the plugin.
Indexing:FacetAttributes places selected product properties and metafields under the record's attributes object and configures them as Algolia facets. The entries use the same aliases and optional modifiers as ProductProperties.
Published metafield definitions marked Filterable are automatically added as product facets without an appsettings entry. Keep FacetAttributes for custom product properties or to explicitly include a metafield even when it is not marked Filterable; explicit entries also take precedence for field modifiers. Publishing, unpublishing, or deleting a metafield definition queues a product rebuild for each enabled store, so existing records and Algolia facet settings update after that background rebuild completes.
{
"Ekom": {
"Algolia": {
"Indexing": {
"FacetAttributes": [
"brand",
"metafield:material",
"metafield:availableSizes"
]
}
}
}
}
This produces values such as:
{
"attributes": {
"brand": "Nike",
"material": "Leather",
"availableSizes": ["M", "L", "XL"]
}
}
Indexing:VariantFacetAttributes maps output facet names to properties on either the variant group or variant node. When variant indexing is disabled, each configured facet is added to the product record as a distinct array of values from all variants. When variant indexing is enabled, facets are stored on individual variant records to support combination-safe filtering. This supports two-level variants such as color groups containing size variants:
{
"Ekom": {
"Algolia": {
"Indexing": {
"Variants": true,
"VariantFacetAttributes": {
"color": "variantGroup:title",
"size": "variant:title"
}
}
}
}
}
For one-level variants where both values are stored on each variant node, use property aliases instead:
"VariantFacetAttributes": {
"color": "variant:color",
"size": "variant:size"
}
Variant facets are stored on each variant record, so combined filters such as color and size must match the same variant. Product facet attributes are inherited by variant records, and a variant attribute overrides a product attribute with the same output name.
With variants enabled, the plugin configures these facets with Algolia's afterDistinct modifier and groups search results by ProductId. This provides product-level facet counts while preserving variant-level combinations. Algolia recommends that facet values are consistent within each distinct group, so validate counts for catalogues where one product has many different variant values. A full product reindex is required after adding or changing facet attributes.
Manual reindexing
Use the backoffice endpoints to rebuild Algolia indexes manually. Both endpoints support GET and POST.
Rebuild all configured store indexes:
POST /umbraco/backoffice/api/EkomAlgoliaBackoffice/RebuildIndexes
Only one full rebuild can run per application instance. A concurrent full-rebuild request returns 409 Conflict until the active product, category, and content rebuilds finish. Manual full rebuilds process products, categories, and content sequentially to limit Algolia indexing pressure. If one stage fails, the remaining stages are still attempted before the rebuild is reported as failed. A store rebuild is also rejected with 409 Conflict while a full rebuild or another rebuild for that store is active. Store rebuilds process products before categories; rebuilds for different stores can run concurrently.
Rebuild one store:
POST /umbraco/backoffice/api/EkomAlgoliaBackoffice/RebuildStoreIndexes?storeAlias=Store
| 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 was computed. 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
- Algolia.Search (>= 7.47.0)
- Ekom.U17 (>= 0.2.299)
- HtmlAgilityPack (>= 1.12.4)
- Microsoft.Extensions.Caching.Memory (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection (>= 10.0.0)
- Microsoft.Extensions.Hosting (>= 10.0.0)
- Microsoft.Extensions.Http (>= 10.0.0)
- Microsoft.Extensions.Options (>= 10.0.2)
- Umbraco.Cms.Api.Management (>= 17.0.0 && < 18.0.0)
-
net8.0
- Algolia.Search (>= 7.47.0)
- Ekom.U10 (>= 0.2.299)
- HtmlAgilityPack (>= 1.11.74)
- Microsoft.Extensions.Caching.Memory (>= 8.0.1)
- Microsoft.Extensions.DependencyInjection (>= 8.0.1)
- Microsoft.Extensions.Hosting (>= 8.0.1)
- Microsoft.Extensions.Http (>= 8.0.1)
- Microsoft.Extensions.Options (>= 10.0.2)
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 |
|---|---|---|
| 0.2.63 | 98 | 9/28/2026 |
| 0.2.62 | 107 | 9/22/2026 |
| 0.2.61 | 99 | 9/18/2026 |
| 0.2.60 | 107 | 9/17/2026 |
| 0.2.59 | 110 | 9/16/2026 |
| 0.2.58 | 108 | 9/16/2026 |
| 0.2.57 | 102 | 9/16/2026 |
| 0.2.56 | 118 | 9/12/2026 |
| 0.2.55 | 107 | 9/11/2026 |
| 0.2.54 | 103 | 9/11/2026 |
| 0.2.53 | 134 | 9/8/2026 |
| 0.2.52 | 114 | 9/7/2026 |
| 0.2.51 | 130 | 9/3/2026 |
| 0.2.50 | 105 | 9/3/2026 |
| 0.2.49 | 116 | 9/3/2026 |
| 0.2.48 | 105 | 9/2/2026 |
| 0.2.47 | 121 | 9/2/2026 |
| 0.2.46 | 126 | 9/2/2026 |
| 0.2.45 | 107 | 9/2/2026 |
| 0.2.44 | 134 | 8/25/2026 |