Vue Storefront is now Alokai! Learn More
Customization

Customization

Once the basics are working, you'll likely want to customize how facets are displayed, support multiple currencies or languages, or extend the normalized response with fields specific to your store.

Facet Configuration

Declaring facet types

Use facetConfig to tell the storefront how to render each facet. A color facet becomes color swatches, a size facet becomes size chips, a price facet becomes a range slider — all driven by the type you assign.

const unifiedApiExtension = createUnifiedExtension({
  config: {
    facetConfig: [
      { names: ['colors', 'Farbe'], type: 'COLOR' },
      { names: ['sizes', 'Größe'], type: 'SIZE' },
      { names: ['category'], type: 'CATEGORY' },
      { names: ['price'], range: true, type: 'PRICE' },
      { names: ['brand'], type: 'MULTI_SELECT' },
    ],
  } satisfies UnifiedConfig,
  // ...
});

Each rule maps one or more Bloomreach field names to a storefront type. Set range: true for range facets (like price) so Bloomreach returns bucketed ranges instead of individual values. By default, all facets allow multiple selections — set multiSelect: false to restrict to single selection.

See the facet config rule reference for all available properties.

Dynamic facet config

For facets that aren't known at build time, use getFacetConfig as a fallback. It's called only when no matching rule is found in facetConfig:

getFacetConfig: (facet) => {
  if (facet.name.startsWith('color')) {
    return { names: [facet.name], type: 'COLOR' };
  }
  return undefined; // Use defaults
},

Filtering facets

You can exclude facets from the response in two ways — by name for simple cases, or with a callback for dynamic filtering:

// Simple: exclude by name
config: {
  excludeFacets: ['internal_score', 'warehouse_id'],
}

// Advanced: filter callback
config: {
  filterFacets: (facet) => facet.count > 0,
}

Category facets

When categoryRoot is set, the category facet shows children of that category instead of the full tree. This is useful when your Bloomreach catalog has a root category that shouldn't be visible to shoppers.

config: {
  categoryRoot: 'root_category_id',
  facetConfig: [
    { names: ['category'], type: 'CATEGORY' },
  ],
}

The category hierarchy is built from Bloomreach's cat_id, cat_name, and parent fields. When a user selects a category, the facet automatically drills down to show its children.

Multi-Currency

To support multiple currencies, list the indexed currency codes in your middleware config:

configuration: {
  discoveryApi: {
    search: {
      currencies: ['EUR', 'GBP', 'USD'],
    },
  },
}

The extension takes care of the rest — it requests per-currency price fields (price_eur, sale_price_eur, etc.) from Bloomreach, picks the right one based on the active vsf-currency cookie, and uses it for sorting too.

Your Bloomreach catalog must include per-currency price fields following the price_<currency> naming convention (lowercase). See the Index Structure guide for details.

If your Bloomreach account uses views for currency-specific pricing, use resolveViewId in the middleware configuration block to map each request to the correct view:

// middleware config
configuration: {
  discoveryApi: { /* ... */ },
  resolveViewId: (context) => {
    const { currency, locale } = context.config.normalizerContext ?? {};
    return currency && locale ? `${locale}_${currency.toLowerCase()}` : undefined;
  },
}

Multi-Language

For locale-specific catalogs, use resolveDomainKey in the middleware configuration block to map the active locale to a Bloomreach domain key:

// middleware config
configuration: {
  discoveryApi: { /* ... */ },
  resolveDomainKey: (context) => {
    const locale = context.config.normalizerContext?.locale ?? 'en';
    return `store_${locale}`;
  },
}

The locale is read from the vsf-locale cookie on each request. The resolved domain key overrides the static domainKey from your environment variables.

Custom Normalizers

The Bloomreach Discovery unified extension uses the same normalizer system as all Alokai unified APIs. You can add custom fields to the response or override normalizers entirely.

Custom fields are added via the addCustomFields function in createUnifiedExtension:

import { createUnifiedExtension } from '@vsf-enterprise/bloomreach-discovery-api';

const unifiedApiExtension = createUnifiedExtension({
  config: {
    defaultCurrency: 'EUR',
    facetConfig: [/* ... */],
  },
  isNamespaced: true,
  normalizers: {
    addCustomFields: [{
      normalizeProductCatalogItem: (context, input) => ({
        myCustomField: input.rawProduct.my_custom_attr,
      }),
    }],
  },
});

For a complete guide on custom fields, overrides, and the defineNormalizer / defineAddCustomFields utilities, see the Normalizers documentation. The Reference page lists all normalizers available for override.

Index Structure

For the expected Bloomreach catalog field names and conventions that make everything work out of the box, see the Index Structure guide.