Skip to content

ProductList container

The ProductList container manages and displays a list of recommended products based on the current product. Its behavior is driven by configuration options such as currentProduct, recId, and routing through routeProduct callbacks.

ProductList container

ProductList container

The ProductList container provides the following configuration options:

Option Type Req? Description
recIdstringYes Unique identifier for the recommendation unit.
currentProductobjectNo Anchor product context (`{ sku: string; price?: number }`). Required fields depend on unit type and price filter operator. See [Product context](#product-context).
currentSkustringNo Deprecated as of v4.0.3. Use `currentProduct` instead.
cartSkusstring[]No Cart SKUs for recommendation context.
userPurchaseHistoryany[]No User Purchase History for recommendation context.
userViewHistoryany[]No User View History for recommendation context.
routeProductfunctionNo Callback function that returns a product URL.
hideHeadingbooleanNo Hide recommendations heading text.
initialDataobjectNo Pre-loaded recommendation data to avoid initial fetch.
pagePlacementstringNo Identifies the placement of the recommendations unit on the page (for example, `product-list`). Used for ACDL analytics event tracking.
slotsobjectNo Slots for customizing heading, thumbnail, price, title, SKU, and footer.

Pass product context with the optional currentProduct prop:

currentProduct?: { sku: string; price?: number };
  • currentProduct.sku — seed product SKU for anchor-based recommendation unit types.
  • currentProduct.price — anchor product price. Required only when the unit uses dynamic or relative price filter operators (Adobe Commerce Optimizer and Adobe Commerce as a Cloud Service only).

Whether each value is required depends on the recommendation unit type and the price filter operator:

Unit typeSeed SKU (currentProduct.sku)Anchor price (currentProduct.price)
Anchor-based (more-like-this, visual similarity, viewed-viewed, viewed-bought, bought-bought)RequiredOnly for dynamic or relative price filter operators
Context-free (most-viewed, most-purchased, and similar popularity or behavioral types)Not requiredNot applicable

A static price filter defines a fixed price range in the filter rule. It does not remove the need for a seed SKU on anchor-based types. Anchor-based units with static price filters work with the seed SKU alone—currentProduct.price is not sent.

  • PDP pages: The Commerce boilerplate block reads currentProduct from the Adobe Client Data Layer (ACDL) product context. You do not need to pass currentProduct in block configuration.
  • Non-PDP pages: There is no product context on PLP, cart, home, and similar pages. For anchor-based unit types, set currentsku in the product-recommendations block configuration. Add currentprice only when the unit uses dynamic or relative price filter operators.
  • Context-free unit types: Omit currentProduct. The dropin renders with recId alone on any page type.

The container selects a GraphQL query based on whether anchor price is present:

  • When currentProduct.price is present (Adobe Commerce Optimizer and Adobe Commerce as a Cloud Service only), the container uses GetRecommendationsByUnitIdsWithCurrentProduct, which supports dynamic and relative price filter operators.
  • When only currentProduct.sku is present, or currentProduct is absent, the container uses GetRecommendationsByUnitIds, which is compatible with all backend types, including PaaS.

On Adobe Commerce Optimizer and Adobe Commerce as a Cloud Service, the dropin sends currentProduct.price whenever it is available from the product context — it does not inspect the unit’s configured filter operator. The extended query is selected whenever price is present; the backend applies the anchor only when the unit’s filter uses a dynamic or relative operator, and ignores it for static or non-price filters.

The ProductListProps interface has the following shape:

export interface ProductListProps extends HTMLAttributes<HTMLDivElement> {
recId: string;
currentProduct?: {
sku: string;
price?: number;
};
/** @deprecated As of v4.0.3. Use `currentProduct` instead. */
currentSku?: string;
initialData?: {
recommendations?: {
results: RecommendationUnitModel[];
totalProducts: number;
};
};
hideHeading?: boolean;
routeProduct?: (item: Item) => string;
cartSkus?: string[];
userPurchaseHistory?: any[];
userViewHistory?: any[];
slots?: {
Heading?: SlotProps;
Footer?: SlotProps;
Title?: SlotProps<{
item: Item;
productUrl: string;
}>;
Sku?: SlotProps<{
item: Item;
}>;
Price?: SlotProps<{
item: Item;
}>;
Thumbnail?: SlotProps<{
item: any;
defaultImageProps: ImageProps;
}>;
};
}

The RecommendationUnitModel object has the following shape:

export interface RecommendationUnitModel {
displayOrder: number;
pageType: PageType;
title: string;
items: Item[];
totalProducts: number;
typeId: string;
unitId: string;
unitName: string;
}
export type PageType = 'Product'; // Always hardcoded to 'Product' per requirements
export interface Item {
uid: string;
sku: string;
name: string;
urlKey: string;
images: ItemImage[];
price: FinalPrice;
priceRange?: {
minimum?: FinalPrice;
maximum?: FinalPrice;
};
visibility: string;
queryType: string;
itemType: string;
inStock?: boolean;
}
interface ItemImage {
label: string;
roles: string[];
url: string;
}
export interface Price {
value: number | null;
currency: string | null;
}
export interface FinalPrice {
final?: {
amount?: Price;
};
}
export interface RecommendationsResponse {
results: RecommendationUnitModel[];
totalResults: number;
}
export interface GraphQLResponse {
errors?: Array<{ message: string }>;
data?: {
recommendations: RecommendationsResponse;
};
}

The ProductList container supports the following slots:

  • Heading - Customize the recommendations heading
  • Footer - Customize the action button area (Add to Cart / View Product)
  • Title - Customize the product title display
  • Sku - Customize the SKU display
  • Price - Customize the price display
  • Thumbnail - Customize the product image display

Each slot receives context data relevant to its purpose, allowing for highly customizable product displays.

The following example demonstrates how to render the ProductList container with recId, currentProduct, and routeProduct callbacks with different slots:

// Render Container
provider.render(ProductList, {
recId: 'recommendation-unit-1',
routeProduct: (item) => `/products/${item.urlKey}`,
currentProduct: { sku: 'ADB150', price: 49.99 },
hideHeading: false,
slots: {
// Example of how to prepend or append to the default Heading
Heading: (ctx) => {
const heading = document.createElement('div');
heading.innerText = 'Slot: Content before default heading';
ctx.prependChild(heading);
const footer = document.createElement('div');
footer.innerText = 'Slot: Content after default heading';
ctx.appendChild(footer);
},
// Example of how to prepend or append to the default Thumbnail
Thumbnail: (ctx) => {
const thumbnail = document.createElement('div');
thumbnail.innerText = 'Slot: Content before default thumbnail';
ctx.prependChild(thumbnail);
const footer = document.createElement('div');
footer.innerText = 'Slot: Content after default thumbnail';
ctx.appendChild(footer);
},
// Example of how to prepend or append to the default Price
Price: (ctx) => {
const price = document.createElement('div');
price.innerText = 'Slot: Content before default price';
ctx.prependChild(price);
const footer = document.createElement('div');
footer.innerText = 'Slot: Content after default price';
ctx.appendChild(footer);
},
// Example of how to replace default Footer with a custom footer based on product type
Footer: (ctx) => {
const wrapper = document.createElement('div');
wrapper.className = 'footer__wrapper';
const addToCart = document.createElement('div');
addToCart.className = 'footer__button--add-to-cart';
wrapper.appendChild(addToCart);
if (ctx.item.itemType === 'SimpleProductView') {
// Add to Cart Button
UI.render(Button, {
children: 'Add to Cart',
onClick: ctx.item.inStock
? () => {
// Call add to cart function from cart/api
console.log('Add to Cart');
}
: undefined,
variant: 'primary',
disabled: !ctx.item.inStock,
})(addToCart);
} else {
// View Product Button
UI.render(Button, {
children: 'Select Options',
onClick: () => {
console.log('Select Options');
window.location.href = ctx.item.urlKey;
},
variant: 'tertiary',
})(addToCart);
}
ctx.replaceWith(wrapper);
},
},
}));
  • Automatic Data Fetching: Fetches recommendations based on currentProduct (or legacy currentSku) and other context
  • Intersection Observer: Automatically tracks when recommendations are viewed
  • Event Publishing: Publishes render and view events for analytics
  • Loading States: Handles loading states during data fetching
  • Empty States: Gracefully handles empty recommendation results
  • Internationalization: Supports multiple languages through i18n
  • Customizable Slots: Extensive customization options for all UI elements