{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-developer-portal/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown","redocly_category":"App development","owner":"MB"},"seo":{"title":"Working with Select Product promotions","description":"How to implement the Select Product promotion effect in a storefront — reading optional items from the basket, presenting choices, and handling edge cases.","keywords":"technical, guide, promotions, select product, optional items, basket, effect"},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"working-with-select-product-promotions"},"children":["Working with Select Product promotions"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Select Product"]}," promotion effect lets customers choose one or more free or discounted items when their basket qualifies for a promotion. This guide explains how it works in the basket model and what your storefront needs to do to support it."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"how-it-works"},"children":["How it works"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Unlike the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Add Product"]}," effect — which automatically inserts items into the basket with no customer input — Select Product surfaces a list of eligible items that the customer actively chooses from."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When a promotion with a Select Product effect is applied to the basket, Norce populates an ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["OptionalItems"]}," collection on each basket row that qualifies. Your storefront reads this collection and renders the choices beneath or alongside the qualifying row."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When the customer picks an item, you call ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["InsertBasketItem"]}," with the full OptionalItems entry. Norce handles the association to the parent row."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Once added, the selected item is persisted to the database and gets its own basket item ID. It is visible in the basket as a child row clearly associated to its parent."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"rendering-the-selection-ui"},"children":["Rendering the selection UI"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The entire selection UI should be driven by basket data. For each basket row:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Check whether the row has an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OptionalItems"]}," collection with entries"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["If it does, render the available choices — typically inline beneath the qualifying row"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["When the customer selects an item, call ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"/api-reference/services/shoppingservice/openapi-v1/basket/paths/~1shopping~11.1~1insertbasketitem/post"},"children":["Insert Basket Item"]}," with the OptionalItems entry as-is"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Norce handles the association to the parent row"]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["Example Basket (with optional items)"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n    \"Id\": 885,\n    \"CustomerId\": null,\n    \"CompanyId\": null,\n    \"SalesContactId\": null,\n    \"StatusId\": 3,\n    \"CurrencyId\": 2,\n    \"CurrencyCode\": \"SEK\",\n    \"Comment\": \"A test basket with select product optional items, fields omitted for size\",\n    ...omitted...\n    \"Items\": [\n        {\n            \"Id\": 1793,\n            \"LineNo\": 1,\n            \"ParentLineNo\": null,\n            \"PartNo\": \"100011\",\n            \"Quantity\": 2,\n            \"Comment\": \"This item has optional items under it\",\n            ...omitted...\n            \"OptionalItems\": [\n                {\n                    \"Id\": 0,\n                    \"LineNo\": 0,\n                    \"ParentLineNo\": null,\n                    \"ProductId\": 27,\n                    \"PartNo\": \"100015\",\n                    \"PriceDisplay\": 80.00,\n                    \"VatRate\": 1.25,\n                    \"Quantity\": 1,\n                    \"Comment\": \"optional basketitem 1 with promotional discount for display\",\n                    ...omitted...\n                    \"PriceDisplayIncVat\": 100.0000,\n                }\n            ],\n            ...omitted...\n            \"AppliedPromotions\": [\n                \"Does not contain the select-product promotion\"\n            ],\n            \"RequirementPromotionIdSeed\": \"59,1,2 - does contain the select product promotion along with others\",\n            \"PriceDisplayIncVat\": 5037.49,\n            ...omitted...\n        }\n    ],\n    ...omitted...\n    \"AppliedPromotions\": [\n        {\n            \"Id\": 59,\n            \"Name\": \"Select product test for machines\",\n            \"ShortDescription\": \"Applied promotions on Basket level contains Select product promotion candidates, even if they are not used yet (appliedamount=0), its productfilter contains the effect products as well.\",\n            ...omitted...\n            \"ProductFilters\": [\n                {\n                    ...omitted...\n                    \"PartNo\": \"100015\"\n                },\n                {\n                    ...omitted...\n                    \"PartNo\": \"100016\"\n                }\n            ],\n            ...omitted...\n            \"ExternalCode\": \"prm_select\",\n            \"AppliedAmount\": 0.00\n        }\n    ],\n    ...omitted...\n}\n\n","lang":"json"},"children":[]}]},{"$$mdtype":"Tag","name":"details","attributes":{},"children":[{"$$mdtype":"Tag","name":"summary","attributes":{},"children":["Example Basket (with added optional item)"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n    \"Id\": 885,\n    \"CustomerId\": null,\n    \"CompanyId\": null,\n    \"SalesContactId\": null,\n    \"StatusId\": 3,\n    \"CurrencyId\": 2,\n    \"CurrencyCode\": \"SEK\",\n    \"Comment\": \"A test basket with a select product effect applied, fields omitted for size\",\n    ...omitted...\n    \"Items\": [\n        {\n            \"Id\": 1793,\n            \"LineNo\": 1,\n            \"PartNo\": \"100011\",\n            \"Quantity\": 2,\n            \"Comment\": \"This item has no longer an optional item, if its only one is already applied\",\n            ...omitted...\n            \"OptionalItems\": []\n            ...omitted...\n            \"AppliedPromotions\": [\n                \"Does not contain the select product promotion, since it will only change the amount for the new item below\"\n            ],\n            \"RequirementPromotionIdSeed\": \"59,1,2 - does contain the select product promotion along with others\",\n            \"PriceDisplayIncVat\": 5037.49,\n            ...omitted...\n        },\n        {\n            \"Id\": 1794,\n            \"LineNo\": 2,\n            \"ProductId\": 27,\n            \"PartNo\": \"100015\",\n            \"PriceDisplay\": 80.00,\n            \"VatRate\": 1.25,\n            \"Quantity\": 1,\n            \"Comment\": \"optional basketitem 1 with promotional discount for display\",\n            ...omitted...\n            \"AppliedPromotions\": [\n                {\n                    \"Id\": 59,\n                    \"Name\": \"Select Product promotion, were the applied ammount is the discount to the price list price\",\n                    \"DiscountCode\": null,\n                    \"AppliedAmount\": 19.00,\n                    \"AppliedAmountIncVat\": 23.75\n                }\n            ],\n            ...omitted...\n            \"PriceDisplayIncVat\": 100.0000,\n        }\n    ],\n    ...omitted...\n    \"AppliedPromotions\": [\n        {\n            \"Id\": 59,\n            \"Name\": \"Select product test for machines\",\n            \"ShortDescription\": \"Applied promotions on Basket level contains applied select product promotion with the total sum from all added optional items.\",\n            ...omitted...\n            \"ProductFilters\": [\n                {\n                    ...omitted...\n                    \"PartNo\": \"100015\"\n                },\n                {\n                    ...omitted...\n                    \"PartNo\": \"100016\"\n                }\n            ],\n            ...omitted...\n            \"ExternalCode\": \"prm_select\",\n            \"AppliedAmount\": 19.00\n        }\n    ],\n    ...omitted...\n}\n\n","lang":"json"},"children":[]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"limitation-promotions-not-tied-to-specific-rows"},"children":["Limitation: promotions not tied to specific rows"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["OptionalItems are only populated when the promotion can be associated to a ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["specific basket row"]}," — for example, \"buy product X, get to choose from Y or Z.\""]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If the promotion condition is not row-specific (for example, a basket total threshold like \"spend over 500 kr\"), Norce cannot attach OptionalItems to a particular row. In this case the OptionalItems collection will be empty, and you need to fall back to reading the eligible product list directly from the promotion object on the basket, then look up those products separately to render the selection."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"contrast-with-add-product"},"children":["Contrast with Add Product"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":""},"children":[]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Select Product"},"children":["Select Product"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Add Product"},"children":["Add Product"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Customer choice"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes — customer picks from a list"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["No — items added automatically"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Location in basket"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Child row, associated to parent"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Added to end of basket"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Persisted to DB"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes, once selected"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["No — in-memory only, no basket item ID"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Tied to a specific row"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes (when condition is row-based)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["No"]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Add Product items can be identified by the absence of an internal basket item ID and by their association to a promotion rather than a parent row."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"handling-promotion-expiry"},"children":["Handling promotion expiry"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A selected item is ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["not"]}," automatically removed from the basket if the promotion stops applying (for example, if the qualifying product is removed or the promotion expires). The item remains in the basket, but the promotion-based price no longer applies — the price will change."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Your storefront should watch for this. The signal to look for: a child basket row with no active promotion effect, on a parent that is not a bundle or package. When this occurs, warn the customer before checkout that the conditions for the selected item have changed."]}]},"headings":[{"value":"Working with Select Product promotions","id":"working-with-select-product-promotions","depth":1},{"value":"How it works","id":"how-it-works","depth":2},{"value":"Rendering the selection UI","id":"rendering-the-selection-ui","depth":2},{"value":"Limitation: promotions not tied to specific rows","id":"limitation-promotions-not-tied-to-specific-rows","depth":2},{"value":"Contrast with Add Product","id":"contrast-with-add-product","depth":2},{"value":"Handling promotion expiry","id":"handling-promotion-expiry","depth":2}],"frontmatter":{"seo":{"title":"Working with Select Product promotions","description":"How to implement the Select Product promotion effect in a storefront — reading optional items from the basket, presenting choices, and handling edge cases.","keywords":"technical, guide, promotions, select product, optional items, basket, effect"},"metadata":{"redocly_category":"App development","owner":"MB"}},"lastModified":"2026-07-10T06:04:35.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/developer-portal/app-development/promotions-select-product","userData":{"isAuthenticated":false,"teams":["anonymous"]}}