Add StoreConnect native bundles to the cart
On this page
This guide is for developers building custom storefront experiences with StoreConnect native bundles — the bundle system that does not require Salesforce CPQ. It explains the endpoint that adds a configured bundle to the cart and the JSON shape it expects.
For the store-operator setup of StoreConnect native bundles, see Product bundles.
:::note This is a session-based storefront endpoint, not part of the bearer-token REST API described in StoreConnect API. It authenticates with the visitor’s storefront session cookie and is submitted as a standard form post. StoreConnect native bundles are always available. :::
:::note This guide covers StoreConnect native bundles only. If your store uses Salesforce CPQ bundles instead, use the endpoints described in Configure and add CPQ bundles to the cart. To add several pre-chosen products to the cart in one request without any bundle, see Direct to cart link. :::
Overview
Unlike CPQ bundles, a StoreConnect native bundle is not built up incrementally in the session. You assemble the entire configuration on the client and submit it as a single JSON document. The endpoint then adds the bundle’s lead (anchor) product plus every selected component to the cart as separate but linked cart items.
The default storefront does this for you: the bundle configurator on the product page (the bundle-v2 script) collects the customer’s selections, serializes them to JSON, and posts the form. This guide documents that same contract so you can drive it from a custom front end.
The endpoint
POST /bundles/add_to_cart
This is a collection route — the lead product is identified inside the payload, not in the path.
Form fields:
bundle_id— the lead (anchor) bundle product ID. Matchesbundle_product_idinside the configuration JSON.quantity— the number of bundles to add. This is the authoritative bundle quantity; each component’s quantity is multiplied by it.bundle_configuration— the configuration as a JSON string (see below).bundle_identifier— optional; supply it to replace an existing bundle in the cart (see Edit mode).
On success the response redirects to the cart. On failure it redirects back with an error message.
The bundle configuration JSON
The bundle_configuration field is a JSON string with this shape:
```json
{ “bundle_product_id”: “01t0p000001eJrlAAE”, “bundle_quantity”: 1, “selected_components”: [ { “product_component_id”: “a2x0p000000AbcdAAE”, “product_id”: “01t0p000001eJsqAAE”, “quantity”: 2, “variant_id”: null, “booking_data”: null, “product_name”: “Coffee beans 250g” }, { “product_component_id”: “a2x0p000000EfghAAE”, “product_id”: “01t0p000001eJtuAAE”, “quantity”: 1, “variant_id”: “01t0p000001eJvwAAE”, “product_name”: “Travel mug” } ] } ```
Top-level fields:
bundle_product_id— the lead (anchor) bundle product ID (required).bundle_quantity— used by the storefront configurator to pre-fill the quantity in edit mode. The quantity actually applied when adding to the cart comes from the top-levelquantityform field, so keep the two in sync.selected_components— the array of chosen components.
Each entry in selected_components:
product_component_id— the ID of the Product Component record that defines this slot in the bundle (required). See Product component object reference.product_id— the ID of the base component product (required).quantity— the per-bundle quantity for this component. The amount added to the cart isquantity × bundle quantity.variant_id— optional. If set, this variant product is added in place of the baseproduct_id.booking_data— optional; required only for bookable components. See Bookable components below.nested_bundle_configuration— optional; a full bundle configuration object for a component that is itself a bundle. See Nested bundles below.product_name— informational only.
Discovering the IDs in Liquid
On a bundle product page you can read the structure from the product drop to build the configuration:
```liquid
{% if product.is_bundle? %} {% for component in product.product_components %} component id: {{ component.id }} base product: {{ component.component_product.id }} required: {{ component.required? }} qty range: {{ component.min_quantity }}–{{ component.max_quantity }} default qty: {{ component.default_quantity }} free qty: {{ component.free_quantity }} {% endfor %} {% endif %} ```
Grouped components are available through product.required_component_groups and product.optional_component_groups; the pricing strategy is on product.bundle_price_strategy.
Bookable components
For a component whose product is bookable, include booking_data:
```json
{ “product_component_id”: “a2x0p000000IjklAAE”, “product_id”: “01t0p000001eJxyAAE”, “quantity”: 1, “booking_data”: { “product_bookable_location_id”: “a3y0p000000MnopAAE”, “booking_start”: “2026-07-01T09:00:00”, “booking_end”: “2026-07-01T11:00:00” } } ```
All three fields must be present and valid or the booking details are ignored.
Nested bundles
A component can itself be a bundle. Provide its full configuration under nested_bundle_configuration, using the same shape as the top-level configuration:
```json
{ “product_component_id”: “a2x0p000000QrstAAE”, “product_id”: “01t0p000001eKabAAE”, “quantity”: 1, “nested_bundle_configuration”: { “bundle_product_id”: “01t0p000001eKabAAE”, “bundle_quantity”: 1, “selected_components”: [ { “product_component_id”: “a2x0p000000UvwxAAE”, “product_id”: “01t0p000001eKcdAAE”, “quantity”: 1 } ] } } ```
The nested bundle inherits the parent bundle’s quantity, and its items are linked to the parent bundle in the cart.
Edit mode
To let a customer change a bundle already in the cart, submit the new configuration along with the existing bundle’s bundle_identifier. The endpoint removes the original bundle’s cart items first, then adds the new configuration as a single atomic replacement.
The storefront reaches edit mode through:
GET /bundles/{lead_product_sfid}/edit?bundle_identifier={identifier}
This re-renders the bundle product page pre-populated from the cart so the customer can adjust their selections, then submits to POST /bundles/add_to_cart with the original bundle_identifier included.
Calling the endpoint
The endpoint is designed to be submitted from the storefront as a standard form post, where the session cookie and CSRF token are already present. From custom JavaScript on the storefront it looks like this:
```js
const configuration = { bundle_product_id: leadProductId, bundle_quantity: 1, selected_components: [ { product_component_id: “a2x0p000000AbcdAAE”, product_id: “01t0p000001eJsqAAE”, quantity: 2 }, ], };
const body = new URLSearchParams(); body.append(“bundle_id”, leadProductId); body.append(“quantity”, “1”); body.append(“bundle_configuration”, JSON.stringify(configuration));
await fetch(“/bundles/add_to_cart”, { method: “POST”, headers: { “X-CSRF-Token”: document.querySelector(‘meta[name=”csrf-token”]’).content, }, body, credentials: “same-origin”, }); ```
Validation — important
:::warning Component group rules — required groups, minimum and maximum option counts, and group quantity limits — are enforced by the storefront configurator in the browser before the form is submitted. The add-to-cart endpoint does not re-check those group constraints. It only confirms that each product and component exists and is available, then adds exactly what the configuration specifies. If you build the configuration yourself, you are responsible for honoring the bundle’s group and quantity rules; the endpoint will not reject a configuration that breaks them. :::
If any referenced product or component is missing or unavailable, the whole request fails and nothing is added.
Reference
- Endpoint:
POST /bundles/add_to_cart(collection route; lead product is in the payload). - Authoritative bundle quantity is the top-level
quantityform field; component quantities are multiplied by it. bundle_configurationis a JSON string:bundle_product_id,bundle_quantity, andselected_components[].- Each component needs
product_component_idandproduct_id;variant_id,booking_data, andnested_bundle_configurationare optional. - Pass an existing
bundle_identifierto replace a bundle already in the cart. - Group and quantity rules are validated client-side only — a custom caller must enforce them.
- StoreConnect native bundles do not require Salesforce CPQ. For the CPQ equivalent, see Configure and add CPQ bundles to the cart.
Was this article helpful?
Thanks for your feedback! It helps us improve our docs.