Ekom.Algolia 0.2.63

dotnet add package Ekom.Algolia --version 0.2.63
                    
NuGet\Install-Package Ekom.Algolia -Version 0.2.63
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Ekom.Algolia" Version="0.2.63" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Ekom.Algolia" Version="0.2.63" />
                    
Directory.Packages.props
<PackageReference Include="Ekom.Algolia" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Ekom.Algolia --version 0.2.63
                    
#r "nuget: Ekom.Algolia, 0.2.63"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Ekom.Algolia@0.2.63
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Ekom.Algolia&version=0.2.63
                    
Install as a Cake Addin
#tool nuget:?package=Ekom.Algolia&version=0.2.63
                    
Install as a Cake Tool

Ekom.Algolia

Nuget Publish 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 SearchForHits requests.
  • Category search service with Algolia SDK SearchForHits requests.
  • 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, and IAlgoliaContentEnricher modify mapped records. Multiple implementations run in ascending Order.
  • IAlgoliaProductFieldConverter converts configured product properties and IAlgoliaContentPropertyValueConverter converts configured Umbraco properties. Converters run in ascending Order; each converter whose CanHandle returns true receives the current value.
  • IAlgoliaProductIndexFilter excludes products before mapping, as shown above.
  • IAlgoliaProductIndexMapper and IAlgoliaCategoryIndexMapper are the public mapper abstractions. AddAlgolia registers 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, and IsVariant.
  • Content: NodeName, Title, Summary, Description, and Url.
  • Images: image_url and ImageUrls.
  • Pricing: Price, PriceWithVat, PriceWithoutVat, PriceFormatted, PriceWithVatFormatted, PriceWithoutVatFormatted, Discounted, OriginalPrice, OriginalPriceFormatted, and Currency.
  • Availability and ranking: Available, ProductRanking, and CategoryRanking.
  • Store context and dates: StoreAlias, Locale, CreatedAt, and UpdatedAt.
  • Categories: categoryPageId, hierarchical_categories.lvl0, additional hierarchy levels when present, and category_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.

  • ProductRanking reads the product ekmAlgoliaRank property and defaults to 0 when missing or invalid.
  • CategoryRanking uses the highest valid ekmAlgoliaRank value across the product's categories and defaults to 0 when 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:

  • |array parses JSON arrays such as ["Web","Store"] into Algolia string arrays.
  • |decimal accepts comma or dot decimal separators, such as 0,1 and 0.0.
  • |striphtml converts direct HTML or rich-text JSON with a markup property to plain text. It removes script and style content, decodes HTML entities, and normalizes tags and whitespace to single spaces.
  • Metafields with Enable Multiple Choice enabled are automatically indexed as string arrays. The |array modifier can still be used to explicitly index other metafields as arrays.
  • Invalid |array, |int, and |decimal values 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
Loading failed