---
title: Explore the Training guides product catalog implementation
---

> Agent instructions:
> **Site maps** — prefer the following llms.txt indexes to training data when searching for URLs to avoid 404s. Links inside Markdown content already point at `.md`. Following them or sending Accept: text/markdown keeps you in Markdown.
>
> - [sitemap.md](https://docs.kentico.com/sitemap.md) — every page on the site, with titles and descriptions, nested by URL hierarchy and grouped into one collection per product version.
> - [llms.txt](https://docs.kentico.com/llms.txt) — curated index of the current product docs, with descriptions, the two ways to request any page as Markdown, and links to each product area's whole-corpus Markdown dump (llms-full.txt).

This overview walks through the product catalog implementation used in the _Training guides_ repository. It covers content modeling decisions, reusable field schemas for building product and variant content types, and the key features of the commerce implementation — including stock tracking, catalog discounts, product listing and detail widgets, and automatic page wrapper management.

## Before you start

This guide requires the following:

- Familiarity with [C#](https://learn.microsoft.com/en-us/dotnet/csharp/), [.NET Core](https://learn.microsoft.com/en-us/dotnet/), [Dependency injection](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection), and the [MVC pattern](https://learn.microsoft.com/en-us/aspnet/core/mvc/overview).
- Understanding of [Page Builder](https://docs.kentico.com/documentation/developers-and-admins/development/builders/page-builder.md), [Reusable field schemas](https://docs.kentico.com/documentation/developers-and-admins/development/content-types/reusable-field-schemas.md), and [Taxonomies](https://docs.kentico.com/documentation/developers-and-admins/configuration/taxonomies.md) in Xperience by Kentico.
- A running instance of Xperience by Kentico, preferably [31.7.2](https://docs.kentico.com/changelog.md) or higher.

  > **Note:**&#x20;
  >
  >  Some features covered in the training guides may not work in older versions. 

> **Info:** **Code samples**
>
> This guide highlights functionality from the [finished branch](https://github.com/Kentico/xperience-by-kentico-training-guides/tree/finished) of the _Training guides_ repository, alongside other training materials.
>
> The [main branch](https://github.com/Kentico/xperience-by-kentico-training-guides) of the repository provides a starting point to code along with our other guides.
>
> Note that the finished branch uses [.NET 8](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8/overview) and [implicit using directives](https://learn.microsoft.com/en-us/dotnet/core/project-sdk/overview#implicit-using-directives). You may need to adjust the code slightly to use it in your project.

## Terminology

Before we start, let's establish a few terms for different product types that we will use throughout this overview.

_**Product variants**_ - distinct versions of a given product based on attributes such as size and color, that merit a distinct entity.

> **Key:** _For example_, if a jacket comes in multiple colors and sizes, a shop would likely track stock for each individual combination, like _small black jacket_, _small gray jacket_, _medium black jacket_, etc.

_**Parent products**_ - products which have _variants_. Parent products are often abstract, providing info that is shared among their variants and representing the relationship between variants, but they cannot be directly purchased or stocked.

> **Key:** _For example_, a shop might sell multiple quantities of the same canned energy drink with _6-pack box_ and _24-pack box_ variants. The variants are tied together by a parent product called _Super Energy_ that contains branding, nutritional information, and a description.

_**Basic products**_ - products which do not have variants. Basic products are top-level products like _parent products_, but they are not abstract. They directly represent something that can be purchased.

> **Key:** _For example_, a shop might sell a coffee mug with the company logo on it. This product requires stock tracking and price information even though it is a top-level product with no parent.

## Content querying and content modeling

With any content management platform, you must decide how to structure data. If you are a developer, you may feel tempted to leave content modeling totally in the hands of solution architects or project managers, but it is important to understand how modeling decisions affect both your development tasks and the ease of delivering a functional project.

When you design infrastructure-specific content types that exist within a digital experience platform like _Xperience by Kentico_, it is important to consider the strengths and friction points of that platform: will the design allow editors to easily work with the content? Does the platform have a straightforward way to query and display a given model, or will it create extra development work?

With the _Training guides_ commerce implementation, our content modeling approach had the following goals:

- Store products in the Content hub, using page wrappers to display them in web channels
- Use separate content items to represent product variants
- Ensure developers can easily query products of multiple content types based on a variety of parameters
- Minimize unused, inapplicable fields during the editing process to prevent confusion

Let's outline scenarios that prioritize editor usability and data querying efficiency, then examine the solution the Training guides model uses to balance them.

### Choose a balanced modeling strategy

> **Note:** **Easiest model for editors**
>
> Each product and variant type has a dedicated content type in Xperience. When an editor wants to create a new _Dog collar_ product, they add a new item of the _Dog collar_ type instead of _Product_.
>
> Parent product types have their own dedicated fields for variants, with the _Combined content selector_ scoped to their specific variant type (e.g., the _Cat food_ product only allows _Cat food variant_ items as variants).
>
> There are no fields that only apply in certain scenarios. For example, abstract parent products do not have a _Product price_ field whose value is ignored in favor of variant prices. If a product or variant has a field, it does something.
>
> **Drawbacks for developers**
>
> - Developers must account for several content types to cover each type's unique fields.
> - Finding the parent products of a filtered set of variants (e.g., all products available in the color red) is difficult and inefficient.
>   - Querying references requires the name of the linking content type and of its field, which are different across each content type (e.g., DogCollarVariants, DogHarnessVariants, DogBandanaVariants, etc.).
>   - As a result, developers must check every _parent product_ type individually.

> **Note:** **Easiest model for developers**
>
> There is one dedicated type that represents all products and variants. By filling in descriptions, names, and various other attributes, editors use this type to represent any category of product. Whether stock or price should be tracked by parent product or variant does not matter because they both use the same type, which contains all fields they could possibly need, and fallbacks are possible through linked items of the same type. It is up to editors to recognize or indicate when each field should apply.
>
> Because there is one product content type, developers always know which content type field holds variants, so retrieving parent products for a mixed set of variants is straightforward.
>
> **Drawbacks for editors**
>
> - Editors have to deal with fields that only apply in certain contexts, which cannot always be hidden with visibility conditions, leading to confusion.
>   - E.g., both _parent products_ and _variants_ have a _price_ field, and both cannot apply at once.
> - Variants for products are difficult to find during selection, and editors must sift through every product in the whole project to find them.
> - Products are difficult to find in the Content hub without searching for them by name.

> **Tip:** **The middle path**
>
> Product attributes exist across several [reusable field schemas](https://docs.kentico.com/documentation/developers-and-admins/development/content-types/reusable-field-schemas.md), which apply to product and variant types where applicable. These schemas add fields to content types and also indicate whether a product belongs to a given category in code based on which schemas are present. For example:
>
> ```csharp title="Use of schemas for boolean evaluation"
> public bool ProductHasVariants(IProductSchema product)
> {
>     if (product is IProductParentSchema parentProduct)
>     {
>         return parentProduct.ProductParentSchemaVariants.Any();
>     }
>
>     return false;
> }
> ```
>
> Each product and variant type has a dedicated content type in Xperience. When an editor wants to create a new _Dog collar_ product, they add a new item of the _Dog collar_ type instead of _Product_. Each of these types implements reusable schemas with the fields it needs.
>
> Developers can easily filter queries by linking references, because parent products will always link their variants via a single schema field (`ProductParentSchemaVariants`).
>
> **Reduced drawbacks**
>
> - Similar to the developer model, editors must look through every content item that uses the _variant schema_ when selecting existing variants for a parent product. However, having separate content types reduces friction.
>   - They can easily sort content items, filtering by _types_ and _taxonomy tags_ to mitigate the issue and find the groups of variants they need.
>   - [Smart folders](https://docs.kentico.com/documentation/business-users/content-hub/content-hub-folders.md#smart-folders) can automatically sort product categories for even faster access during selection and editing.
> - As in the editor model, developers need to implement type-specific logic, but schemas mitigate the issue.
>   - Most of the code logic relies on the schemas, and developers only need to dive into type-specific fields occasionally, such as when assembling view models.
>
> ```csharp title="Working with type-specific fields"
> private async Task<ProductViewModel> GetCatFoodViewModel(CatFood catFoodProduct, CatFoodVariant? catFoodVariant)
> {
>     // Shared logic that does most of the work based on shared schemas
>     var model = await GetGenericProductViewModel(catFoodProduct, catFoodVariant);
>
>     // Small addition to include a type-specific field that is not from a schema
>     model.ProductOtherDetails = new HtmlString
>             (("Ingredients:<br/>" + string.Join("<br/>", catFoodVariant?.CatFoodVariantFormulation
>                 .Select(formulation => formulation.PetFoodFormulationIngredients) ?? []))
>             ?? string.Empty);
>
>     return model;
> }
> ```

### Training guides commerce schemas

You likely noticed that the Training guides product model relies heavily on reusable field schemas. The schemas act as building blocks to create our individual product content types, and allow us to compose most of each content type with minimal type-specific fields. Let's take a closer look at how they work.

#### Product schema - `IProductSchema`

Indicates that a type represents a product in code, and provides basic fields that all parent products, variants, and basic products should have:

- **Product name** - The name of a product or variant
- **Product images** - One or more images of a product or variant
- **Product text** - Description of a product or variant

#### Product parent schema - `IProductParentSchema`

Designates a type as a parent product with variants in code, and provides the field to hold the variants:

- **Variants** - One or more content items that use the _Product variant schema_, representing the parent product's variants

#### Product variant schema - `IProductVariantSchema`

Signifies that a type represents a product variant in code and allows items in the _Variants_ selector of parent products:

- **Variant code name** - A unique code name used for URLs and variant selection

#### Product price schema - `IProductPriceSchema`

Specifies that a product or variant has a price (assignable to top-level product or variant):

- **Price** - The price of the product or variant
- **Discount category** - One or more taxonomy tags, representing discount categories for promotions

#### Product SKU schema - `IProductSkuSchema`

Provides a SKU code field for products or variants and indicates that stock should be tracked for the class in code:

- **SKU Code** - A unique stock-keeping unit code identifying the product or variant

#### Product shipping schema - `IProductShippingSchema`

Indicates that a product or variant has shipping-related attributes, allowing for physical and digital variants of the same product:

- **Product requires shipping** - Whether and how the product requires shipping (e.g., to customer, to store, both, no shipping required)
- **Shipping weight** - The weight of the product, including packaging
- **Weight unit** - The unit of the shipping weight (e.g., kg, g, lb, oz)

#### Amount schema - `IAmountSchema`

Specifies that a product or variant is available in a certain measured amount (e.g., 100mL, 5lb):

- **Amount** - The numerical value of the amount
- **Product measurement unit** - The unit that the numerical value applies to

#### Color/Pattern schema - `IColorPatternSchema`

Assigns a color and/or pattern to a product or variant:

- **Color/Pattern** - One or more taxonomy tags representing the color and/or pattern

#### Material schema - `IMaterialSchema`

Includes a material attribute for a product or variant:

- **Material** - Taxonomy tag(s) representing the material

#### Size schema - `ISizeSchema`

Designates that a product or variant comes in a certain size:

- **Size** - A taxonomy tag representing the size of the product or variant

#### Application

Let's contextualize these schemas with an example. Imagine you are creating content types to represent **T-shirt** products and their variants. (We'll use a **✓** icon to indicate that a given schema is broadly recommended, and a **?** icon to indicate that a schema could apply depending on the given scenario.)

- T-shirt (parent product)
  - **✓ Product schema** for basic name, image, etc.
  - **✓ Product parent schema** so we can create variants for different sizes and colors
  - **? Product price schema** to store price **if** all sizes and colors cost the same, and should not be possible to separately discount
  - **? Product shipping schema** to set shipping info **if** the weight difference between shirt sizes is not large enough to affect shipping costs
  - **✓ Material schema** so customers can filter the product listing by material

- T-shirt variant
  - **✓ Product schema** for basic name, image, etc. (each color variant, at least, will need its own name and image)
  - **✓ Product variant schema** to make the different sizes and colors selectable as variants of a parent product
  - **✓ Product SKU schema** to track the available stock of a specific variant
  - **? Product price schema** to store price **if** sizes and colors have different prices, or should be possible to separately discount
  - **? Product shipping schema** to set shipping info **if** the different variants will have different shipping costs
  - **✓ Color/pattern schema** to represent the color of a specific variant and allow filtering in product listings
  - **✓ Size schema** to represent the size of a specific variant and allow filtering in product listings

Let's examine how these might apply at a more general level, with the [terminology from earlier](#terminology):

- **Parent products** should implement the _Product schema_ and the _Product parent schema_, but **not** the _Product variant schema_.
- **Variants** should implement the _Product schema_ and the _Product variant schema_, but **not** the _Product parent schema_.
- **Basic products** should implement the _Product schema_ but **neither** the _Product parent schema_ nor the _Product variant schema_.

> **Tip:** **Tips for commerce schemas**
>
> - Add your own schemas for attributes that are not here but could be shared by multiple products, such as:
>   - A _Manufacturer schema_ with a field linking to details about the brand, typically applied to parent products to avoid redundant entry on variants
>   - An _Accessory schema_ that allows selection of bundled add-on products
>   - A _Product image_ schema, if you prefer to specify which product and variant types provide images, instead of including them by default in the Product schema
> - If manufacturers have different requirements for the same category of product, consider creating multiple content types, e.g.:
>   - T-shirt (price by variant)
>   - T-shirt (flat price)

## Browse the catalog

Now that we've gone over the content model for products, let's start with a video tour of the implementation, followed by additional information on the key components in the text below.

🎮 [Tour the Training guides product catalog implementation (interactive demo)](https://demo.arcade.software/8IuJQsDWt94Vr6L2P2g0)

### Stock module

The [finished branch](https://github.com/Kentico/xperience-by-kentico-training-guides/blob/finished/) of the Training guides repository includes a basic stock tracking example similar to the [documentation's example](https://docs.kentico.com/documentation/developers-and-admins/digital-commerce-setup/model-product-catalog/model-product-stock.md), with some minor name changes and code style adjustments.

The _Training guides_ module makes the following key changes:

- A [class override](https://github.com/Kentico/xperience-by-kentico-training-guides/blob/finished/src/TrainingGuides.Entities/Classes/Overrides/ProductAvailableStockInfoOverride.cs), which configures the stock class for Continuous Integration
- A field to hold the SKUCode value directly in the [available stock class](https://github.com/Kentico/xperience-by-kentico-training-guides/blob/finished/src/TrainingGuides.Entities/Classes/ProductAvailableStock/ProductAvailableStockInfo.generated.cs), to reduce extra querying in a hypothetical integration with warehouse inventory software
- Additional handlers for [draft updates and deletion](https://github.com/Kentico/xperience-by-kentico-training-guides/blob/finished/src/TrainingGuides.Web/Features/Commerce/EventHandlers/ProductStockCreationHandlerModule.cs), to avoid orphaned stock when content items are deleted, and keep SKU codes up to date
- Evaluation based on the _Product SKU Schema_ (`IProductSkuSchema`) to determine whether to create a stock entry

See the code files behind the custom stock module in the repo:

- [Product stock admin files](https://github.com/Kentico/xperience-by-kentico-training-guides/tree/finished/src/TrainingGuides.Admin/Pages/ProductStock)
- [Product stock event handler](https://github.com/Kentico/xperience-by-kentico-training-guides/blob/finished/src/TrainingGuides.Web/Features/Commerce/EventHandlers/ProductStockCreationHandlerModule.cs)

> **Tip:** For more complex stock tracking scenarios, consider expanding this module with one or more classes to represent different warehouses and stores, tracking stock separately for each. You can see examples of building a UI for multiple levels of related objects in the [customization guides](https://docs.kentico.com/guides/development/customizations-and-integrations/add-channels-to-module.md).

### Catalog discount

The implementation includes a taxonomy-based catalog discount, as described in [Catalog discount documentation](https://docs.kentico.com/documentation/developers-and-admins/digital-commerce-setup/promotions/catalog-discounts.md), operating based on the `ProductPriceSchemaDiscountCategory` field of the _Product price schema_.

This allows editors to tag products for specific sale and discount events, such as _Anniversary sale_ or _Holiday special_. Then, they can reference the same taxonomy tags from a catalog discount with its own start and end dates, meaning the discount will automatically stop affecting the product without the need to remove tags.

See the code files for catalog discounts in the repository:

- [Code files for catalog discount](https://github.com/Kentico/xperience-by-kentico-training-guides/tree/finished/src/TrainingGuides.Web/Features/Commerce/PriceCalculation)

### Product widgets

The Training guides product catalog contains widgets that handle product display, both for _listing_ and _detail_ scenarios. We created both widgets using the [KentiCopilot web development plugin](https://github.com/Kentico/xperience-by-kentico-kenticopilot/tree/main/plugins/kentico-web-development) and refined them further with AI assistance using the [documentation MCP server](https://docs.kentico.com/documentation/developers-and-admins/installation/mcp-server.md) for context.

#### Listing widget with filtering

The _Product listing_ widget displays an array of `ProductPage` wrappers that live under the page where it exists, or under a specified page, depending on editor configuration. It allows editors to specify how member-only pages are handled, and to configure CTA text for products and sign-in.

It includes taxonomy-based filtering that uses **AND** or **OR** logic between categories, depending on editor configuration. The filtering operates on taxonomies applied to product types via the _Material schema_ and _Color/Pattern schema_, and in cases of parent/variant products, filters on the attributes of both.

See the listing widget's code in the Training guides repo:

- [Listing widget code files](https://github.com/Kentico/xperience-by-kentico-training-guides/tree/finished/src/TrainingGuides.Web/Features/Commerce/Products/Widgets/ProductListing)
- [Listing widget JavaScript](https://github.com/Kentico/xperience-by-kentico-training-guides/blob/finished/src/TrainingGuides.Web/wwwroot/PageBuilder/Public/Widgets/ProductListingWidget/product-listing-widget.js)

> **Tip:** To learn more about the details of the filtering implementation, you can follow along with the [product filtering example](https://docs.kentico.com/guides/development/commerce/implement-faceted-filtering-for-product-catalog.md).

#### Detail widget

The _Product_ widget displays the details of a product, allowing editors to choose between the page the widget exists on and a selectable `ProductPage`. It handles both _basic products_ and _parent products with variants_. Editors can also configure whether variants are displayed, whether to include variant detail text, and an optional call-to-action link.

If the product has multiple images, customers can click through the gallery. If the product has variants and the widget is set to display them, they can click between variant thumbnails.

Find the code behind the Product widget in the repository:

- [Product widget code files](https://github.com/Kentico/xperience-by-kentico-training-guides/tree/finished/src/TrainingGuides.Web/Features/Commerce/Products/Widgets/ProductWidget)

### Product service

The `ProductService` underlies much of the key functionality in both widgets, handling data retrieval, filtering, secured content, and view model assembly.

Explore the product service's code in the repo:

- [Product service](https://github.com/Kentico/xperience-by-kentico-training-guides/blob/finished/src/TrainingGuides.Web/Features/Commerce/Products/Services/ProductService.cs)

### Automatic page wrapper module

The product catalog implementation includes an event handler that automatically manages page wrappers for content hub products, saving editors from tedious busywork. It creates, publishes, unpublishes, and deletes `ProductPage` pages, manages their parent `StoreSection` pages, and automatically assigns page templates containing the aforementioned listing and detail widgets via API.

The handler reacts to both _basic products_ and _parent products_, but not _variants_.

> **Tip:** Walk through the process of creating this functionality in [our product page wrapper material](https://docs.kentico.com/guides/development/commerce/create-page-wrapper-for-ch-products.md).

Look over the page wrapper handler code in the Training guides repository:

- [Product page wrapper handler](https://github.com/Kentico/xperience-by-kentico-training-guides/blob/finished/src/TrainingGuides.Web/Features/Commerce/EventHandlers/ProductPageWrapperHandlerModule.cs)

## What's next?

Clone and run the [finished branch](https://github.com/Kentico/xperience-by-kentico-training-guides/tree/finished) of the _Training guides_ repository to see these features in action.

For more commerce-related topics from the architecture standpoint, take a look at:

- [Model Page Builder components for commerce](https://docs.kentico.com/guides/architecture/content-modeling/model-website-presentation-components/model-commerce-presentation-components.md)
- [Upgrade - Commerce features overview](https://docs.kentico.com/guides/upgrade-to-xbyk/upgrade-from-kx13/upgrade-commerce-features-overview.md)

If you haven't already, check out our other commerce-related guides for developers:

- [Create product page wrappers for Content hub products](https://docs.kentico.com/guides/development/commerce/create-page-wrapper-for-ch-products.md)
- [Implement product display patterns with KentiCopilot](https://docs.kentico.com/guides/development/commerce/product-display-patterns-kenticopilot.md)
- [Implement faceted filtering for product catalog](https://docs.kentico.com/guides/development/commerce/implement-faceted-filtering-for-product-catalog.md)

If you have any questions or scenarios you'd like us to cover in the future, please let us know with the **Send us feedback** button at the bottom of this page.
