---
title: Custom facets in the marketing overview
slug: commerce-connect-14/docs/custom-facets-in-the-marketing-overview
docTags: 
createdAt: 2026-07-23T10:25:34.370Z
---

Many ecommerce sites have a large number of campaigns and promotions running. Facets in Optimizely Commerce Connect let users apply filters to campaigns and discounts (promotions), for easier location of specific items in the **Campaign** view. 

Key components mentioned here are available in the `EPiServer.Commerce.Shell.Facets` namespace.

### FacetGroup

`FacetGroup` contains properties that identify a group of items by which to filter a campaign and settings to configure a group. Initialize a `FacetGroup` through a constructor.

```csharp
public FacetGroup(string id, string name, IEnumerable<FacetItem> items, FacetGroupSettings settings)
      {
        Id = id; // the id of facet group
        Name = name; // the name of facet group
        Items = new List<FacetItem>(items); // the list facet item 
        Settings = settings; // settings to configuration a facet group
      }
```

### FacetGroup settings

`FacetGroupSettings` contains properties to configure a facet group. Initialize these properties through a constructor.

```csharp
public FacetGroupSettings(
      FacetSelectionType selectionType,
      int itemsToShow,
      bool collapsible,
      bool hasIcons,
      bool showMatchingItems,
      IEnumerable<string> dependsOn)
      {
        SelectionType = selectionType; // Determine the selection type of facet group, single or multiple through FacetSelectionType enum
        ItemsToShow = itemsToShow; // The number to determine how many facet items will be show as default. If there are more than facet items, show more option will be available
        Collapsible = collapsible; // Determine the facet group is collapsible or not
        HasIcons = hasIcons; // Determine the icon of facet items are displayed or not
        ShowMatchingItems = showMatchingItems; // Determine the number of matching filtered items are shown or not
        DependsOn = dependsOn; // List of facet group that current group depends on. That mean changes the list facet group could affect the number of matching filterd items in the current group
      }
```

### FacetItem

`FacetItem` contains properties that identify a facet item in the facet group, which is a value to a filtered campaign. Initialize these properties through a constructor.

```csharp
public FacetItem(string id, string name, string iconClass = "")
      {
        Id = id; // the id of facet item that display in the url as a value
        Name = name; // the name of facet item that shown in the facets widget
        IconClass = iconClass; // the css class that determining the icon of facet item. Default it's empty
      }
```

## Custom facets Commerce Connect 12.10 and higher

To customize facet groups, create a class inheriting from `FacetGroupModifier`, then register it on one of your initialization modules.

**Example:** Displaying four markets by default in the market facet group, instead of three.

```csharp
public class CustomFacetGroupModifier : FacetGroupModifier
      {
        public override IEnumerable<FacetGroup> ModifyFacetGroups(IEnumerable<FacetGroup> facetGroups)
          {
            var marketFacetGroup = facetGroups.FirstOrDefault(f => f.Id == CampaignFacetConstants.MarketGroupId);
            if (marketFacetGroup != null)
              {
                marketFacetGroup.Settings.ItemsToShow = 4;
              }
            return facetGroups;
          }
      }
```

**Example:** Registering the facet.

```csharp
public void ConfigureContainer(ServiceConfigurationContext context)
      {
        var services = context.Services;
        services.AddSingleton<FacetGroupModifier, CustomFacetGroupModifier>();
      }
```

## Custom facets Commerce Connect 12.9 and lower

First, you need a custom facet based on a campaign facet. Here we create a custom facet that:

- Clears all built-in groups.
- Has a facet group that filters by a campaign's last modified date. This user can choose last modified day, week, or month.

```csharp
public class CustomFacet : CampaignFacet
      {
        public CustomFacet(
                            FacetFactory facetFactory, 
                            IMarketService marketService, 
                            LocalizationService localizationService
                          ) : base(facetFactory, marketService, localizationService)
          {
            // Clear all built-in facet groups
            Groups.Clear();
            Groups.Add(facetFactory.CreateFacetGroup(
              "lastmodified",
              "Last Modified",
              new List() 
                {
                  facetFactory.CreateFacetItem("day", "Today"),
                  facetFactory.CreateFacetItem("week", "A Week Ago"),
                  facetFactory.CreateFacetItem("month", "A Month Ago")
                },
              new FacetGroupSettings(FacetSelectionType.Single, 0, true, true, false, Enumerable.Empty<string>())));
          }
      }
```

This example shows that the last modified facet group is a single selection with three options: day, week, or month.

```csharp
new FacetGroupSettings(FacetSelectionType.Single, 0, true, true, true /*showMatchingItems*/, Enumerable.Empty<string>())
```

:::hint{type="info"}
**Note**
To show a item numbers that indicate how many campaigns are filtered, in `FacetGroupSettings`, set `showMatchingItems` to **true**.
:::

To use a `CustomFacet`, follow these steps.

1. Create a rest store named `customfacet` that returns a custom facet.

```csharp
[RestStore("customfacet")]
        public class CustomFacetStore : RestControllerBase
          {
            FacetFactory _facetFactory;
            IMarketService _marketService;
            LocalizationService _localizationService;
            public CustomFacetStore(
              FacetFactory facetFactory, 
              LocalizationService localizationService, 
              IMarketService marketService)
              {
                _facetFactory = facetFactory;
                _marketService = marketService;
                _localizationService = localizationService;
              }
            public RestResult Get(string id, string facetString, ContentReference parentLink)
              {
                return Rest(new CustomFacet(_facetFactory, _marketService, _localizationService));
              }
          }
```

:::hint{type="info"}
**Note**
To show matching items, use the facet query handler to calculate matching numbers.
:::

```csharp
public RestResult Get(string id, string facetString, ContentReference parentLink)
          {
            var customFacet = new CustomFacet(_facetFactory, _marketService, _localizationService);
            var contentLoader = ServiceLocator.Current.GetInstance();
            var facetQueryHandler = new FacetQueryHandler();
            facetQueryHandler.CalculateMatchingNumbers(
              contentLoader.GetChildren(parentLink), 
              customFacet.Groups, 
              facetString, 
              new GetContentsByFacet[] { new GetCampaignsByLastModified() });
            return Rest(customFacet);
          }
```

2. Register the custom facet store and override the existing store `epi.commerce.facet`. Create a module initializer. If an initializer exists, edit it.

```csharp
define(
          [
            "dojo/_base/declare",
            "epi/_Module",
            "epi/routes"
          ], 
          function (declare, _Module, routes) 
            {
              return declare([_Module], 
                              {
                                initialize: function () 
                                  {
                                    this.inherited(arguments);
                                    var registry = this.resolveDependency("epi.storeregistry");
                                    // remove existing facet store
                                    if (registry.get("epi.commerce.facet")) 
                                      {
                                        delete registry._stores["epi.commerce.facet"];
                                      }
                                    // register the custom facet
                                    registry.create("epi.commerce.facet", routes.getRestPath(
                                      { moduleArea: "app", storeName: "customfacet" } ));
                                  }
                              }
                            );
            }
              );
```

3. If you do not register a module initializer, you need to update or create the project's `module.config` file, as shown in the example below.

```xml
<?xml version="1.0" encoding="utf-8"?>
        <module loadFromBin="false">
          <assemblies>
            <add assembly="OptimizelySite5" />
          </assemblies>
          <dojo>
            <paths>
              <add name="app" path="Scripts" />
            </paths>
          </dojo>
          <clientModule initializer="app.ModuleInitializer">
            <moduleDependencies>
              <add dependency="Commerce" type="RunAfter" />
            </moduleDependencies>
          </clientModule>
        </module>
```

After following the steps above, the **Campaign** view displays a custom facet on the left side, and the built-in groups have been removed. You can interact in the UI, but still need to filter campaigns.

4. Create a function to filter campaigns by last modified date, based on `GetContentsByFacet`.

```csharp
public class GetCampaignsByLastModified : GetContentsByFacet
          {
            public override string Key { get { return "lastmodified"; } }
            public override IEnumerable<IContent> GetItems(IEnumerable<IContent> items, IEnumerable<string> facets)
              {
                return items.Where(item => !(item is SalesCampaign) || AvailableFor((SalesCampaign)item, facets));
              }
            private bool AvailableFor(SalesCampaign item, IEnumerable<string> facets)
              {
                var changed = DateTime.UtcNow - item.Changed;
                var filterString = facets.FirstOrDefault();
                var isAvailable = false;
                switch (filterString)
                  {
                    case "day":
                      isAvailable = changed.Days < 1;
                      break;
                    case "week":
                      isAvailable = changed.Days < 7;
                      break;
                    case "month":
                      isAvailable = changed.Days < 30;
                      break;
                  }
                return isAvailable;
              }
          }
```

5. Customize the query to return `GetCampaignsByLastModified`, by overriding `GetSalesCampaignChildrenQuery`. 

```csharp
[ServiceConfiguration(typeof(IContentQuery))]
        public class CustomGetSalesCampaignChildrenQuery : GetSalesCampaignChildrenQuery
          {
            public CustomGetSalesCampaignChildrenQuery(
              IContentQueryHelper queryHelper, 
              IContentRepository contentRepository, 
              LanguageSelectorFactory languageSelectorFactory, 
              CampaignInfoExtractor campaignInfoExtractor, 
              FacetQueryHandler facetQueryHandler) 
              : base(queryHelper, contentRepository, languageSelectorFactory, campaignInfoExtractor, facetQueryHandler){}
        
            public override int Rank
              {
                // need set rank to higher
                get { return 1000; }
              }
        
            protected override IEnumerable<GetContentsByFacet> FacetFunctions
              {
                get
                  {
                    return new GetContentsByFacet[] 
                      {
                        new GetCampaignsByLastModified()
                      };
                  }
              }
          }
```

Here is an example of the customized **Campaign** view.

![](https://api.archbee.com/api/optimize/BrbvVORKJ5iIeSMZye5kI/rmXm8TvKhe9o3SESy4X8N_1be6dc5-customfacet.png)
