--- title: Recommenders description: Configure Luma recommendation strips placed in your pages, and the popup shown after a product is added to the cart. slug: luma/widgets/recommenders docKind: reference hub: luigisbox-ai --- A recommender renders products from a `recommender` [surface](/concepts/domain-model/#surfaces-and-widgets). Each one has its own folder, `integration/recommender /`, and an integration can have any number of them. Two kinds: - **Inline** — a strip placed relative to an element of your page, on the pages it matches. - **Popup** — a dialog opened after the shopper adds a product to the cart. ## Inline ```ts // integration/recommender similar/_config.ts export default { surface: 'lbs_recommender_similar', size: 8, fields: ['@title', 'url', 'image_url', 'price'], urlMatch: '^/p/', anchor: '.product-detail', position: 'after', reserve: 320, }; ``` ```ts // integration/recommender similar/_init.ts import type { RecommenderConfigType } from '@/types'; export default (c: RecommenderConfigType) => { // The product on this page, as the recommendation's anchor. c.items = () => { const id = document.querySelector('[data-product-id]')?.getAttribute('data-product-id'); return id ? [`product/${id}`] : []; }; }; ``` ## Popup after "add to cart" ```ts // integration/recommender cart/_config.ts export default { surface: 'lbs_recommender_xsell_basket_popup', size: 4, fields: ['@title', 'url', 'image_url', 'price'], confirm: '.cart-notice.is-visible', }; ``` ```ts // integration/recommender cart/_init.ts import type { RecommenderConfigType } from '@/types'; export default (c: RecommenderConfigType) => { c.intent = (target) => { const id = target.closest('.add-to-cart')?.getAttribute('data-product-id'); return id ? `product/${id}` : null; }; }; ``` The popup opens when a click that `intent` recognises is followed, within `windowMs`, by an element matching `confirm` — your site's own "added to cart" notice. It does not open when the add fails. ## Keys | Key | Default | | |---|---|---| | `surface` | — | Required. A `recommender` surface; it decides the model | | `size` | — | Required. The number of products | | `type` | `_global.itemType` | The object type recommended | | `fields` | — | [Fields](/discovery/fields/) the tiles need | | `excludeItems` | `false` | Also exclude the anchor products from the results | | `urlMatch` | — | Inline: a regular expression on the URL's path | | `anchor` | — | Inline: CSS selector of the element the strip is placed relative to, or candidates, most specific first | | `position` | — | Inline: `prepend`, `append`, `before` or `after` | | `settleMs` | — | Inline: wait until the anchor stops changing for this long | | `reserve` | — | Inline: height in pixels reserved while loading, so the page does not jump | | `rootClass` | — | Inline: classes of yours put on the strip's element | | `confirm` | — | Popup: CSS selector of your "added to cart" notice | | `windowMs` | `10000` | Popup: the longest time from the click to the notice | Set in `_init.ts`: | Hook | | |---|---| | `items()` | The anchors: products the recommendation is about, as typed IDs (`product/sku-1001`). `[]` means no strip on this page. | | `active(url)` | Whether the strip belongs on this URL, for URLs a regular expression cannot describe | | `intent(target)` | Popup, required: given a click target, the typed ID of the product being added, or `null` when it is not an add-to-cart control | An inline strip loads when it comes within about two screens of the viewport, after the page has loaded. A recommender that returns nothing, or fails, takes no space. ## Templates and text Override `recommender-box-default`, `recommender-result-default` and `recommender-popup` — see [Templates](/luma/templates/). The heading is the translation `recommender.title`; set it per recommender in its folder's `_translations.ts`. ## See also - [Recommendations](/discovery/recommendations/) — the request a recommender sends