Explore the Training guides product catalog implementation
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#, .NET Core, Dependency injection, and the MVC pattern.
- Understanding of Page Builder, Reusable field schemas, and Taxonomies in Xperience by Kentico.
- A running instance of Xperience by Kentico, preferably 30.11.1 or higher.
Some features covered in the training guides may not work in older versions.
Code samples
This guide highlights functionality from the finished branch of the Training guides repository, alongside other training materials.
The main branch of the repository provides a starting point to code along with our other guides.
Note that the finished branch uses .NET 8 and 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.
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.
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.
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
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.
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.
The middle path
Product attributes exist across several reusable field schemas, 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:
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 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.
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:
- 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.
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.
Stock module
The finished branch of the Training guides repository includes a basic stock tracking example similar to the documentation’s example, with some minor name changes and code style adjustments.
The Training guides module makes the following key changes:
- A class override, which configures the stock class for Continuous Integration
- A field to hold the SKUCode value directly in the available stock class, to reduce extra querying in a hypothetical integration with warehouse inventory software
- Additional handlers for draft updates and deletion, 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:
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.
Catalog discount
The implementation includes a taxonomy-based catalog discount, as described in Catalog discount documentation, 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:
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 widget creation tools and refined them further with AI assistance using the documentation MCP server 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:
To learn more about the details of the filtering implementation, you can follow along with the product filtering example.
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 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:
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.
Walk through the process of creating this functionality in our product page wrapper material.
Look over the page wrapper handler code in the Training guides repository:
What’s next?
Clone and run the finished branch of the Training guides repository to see these features in action.
For more commerce-related topics from the architecture standpoint, take a look at:
If you haven’t already, check out our other commerce-related guides for developers:
- Create product page wrappers for Content hub products
- Implement faceted filtering for product catalog
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.