Mastering Multi-Product Bundles: Add Multiple Items & Quantities to Checkout on Shopify

Hey there, fellow store owners! As a Shopify migration expert, I spend a lot of time diving into the nitty-gritty of what makes our stores tick, and that often means sifting through some fantastic community discussions. Recently, a thread popped up that really caught my eye – "Displaying a list of products with quantities" – started by a user named Conehead. It’s a common challenge, and the community came through with some brilliant solutions.

Conehead’s dilemma was straightforward but powerful: how to present a list of, say, four specific products, let customers pick quantities for each, and then, with a single "Buy Now" click, whisk them straight to checkout with all those chosen items in their cart. Sounds simple, right? But as Conehead found, trying to get AI-generated code to play nicely with a Shopify theme for this kind of multi-item functionality can be a real headache.

Why Standard Solutions Fall Short (and What Works)

The core issue, as Recomento in the thread pointed out, is that standard single-product forms in Shopify are designed to submit just one product ID and quantity at a time. This is why "AI-generated code usually fails" – it often doesn't account for Shopify's specific requirements for batch-adding items. What we need is a way to tell Shopify, "Hey, add ALL these items with their specific quantities, simultaneously!"

This "Build Your Own Bundle" mechanic is a fantastic way to boost your Average Order Value (AOV), and thankfully, the community offered up two robust, code-based solutions that integrate seamlessly into your Shopify theme. Both leverage Shopify's native capabilities, moving beyond basic forms to handle multiple products at once.

Option 1: The Quick & Direct Route with a Custom Section

Ploqo initially offered a solution using a "Custom Liquid" section, which Conehead loved for its immediate effectiveness. The beauty of this approach, and one that Conehead specifically asked about, is making it a proper, organized section file. This keeps your code clean and makes it easy to manage via the Shopify Theme Editor.

This method uses a direct URL redirect to the cart, appending all selected items and quantities. It’s straightforward and effective for many use cases. Here’s the code for a custom section file, let's call it sections/quantity-list.liquid:

{%- for p in section.settings.products -%} {%- assign v = p.selected_or_first_available_variant -%} {%- if v -%}
{{ p.title }} {{ v.price | money }}
{%- endif -%} {%- endfor -%}
{% schema %} { "name": "Quantity list", "settings": [ { "type": "product_list", "id": "products", "label": "Products", "limit": 10 }, { "type": "text", "id": "button_label", "label": "Button label", "default": "Buy now" } ], "presets": [{ "name": "Quantity list" }] } {% endschema %}

Implementing the Quantity List Section:

Here’s how you can add this to your theme, just like Ploqo detailed:

  1. Go to your Shopify Admin, navigate to Online Store > Themes.
  2. Click the … button next to your current theme and select Edit code.
  3. Edit code in the theme menu
  4. In the code editor, click the sections folder, then click the Add a new section icon (or New File).
  5. Name the new file quantity-list.liquid and press Enter.
  6. New file in the sections folder
  7. Paste the code block above into this new file and click Save.
  8. Code in the new section file
  9. Now, go back to your theme customization screen (Online Store > Themes > Customize).
  10. On the page where you want the list to appear, click Add section under Template.
  11. Search for "Quantity list" and click it.
  12. Quantity list in the section list
  13. In the section settings, under Products, you can pick the specific products you want to feature. You can also customize the "Buy now" button label.
  14. Products picker in the section settings
  15. Click Save.

This method is quick to implement, but remember NoahMueller’s excellent advice: always test mixed quantities, zeroed rows, and products that might become unavailable. Confirm the checkout contains exactly what the customer selected to avoid support headaches later.

Option 2: The Robust & Flexible Approach with Cart AJAX API

For a more advanced and robust solution, Recomento provided a fantastic implementation using Shopify’s native Cart AJAX API (/cart/add.js). This is often preferred because it handles the addition of multiple items in a single, atomic network request. This means no race conditions or partially filled carts, and it gives you more control over the process, including better error handling.

This solution also comes as a custom section, making it super friendly for your theme customizer. Here’s the code for sections/multi-product-bundle.liquid:

{{ section.settings.heading | default: 'Select Your Products' }}

{% for block in section.blocks %} {% assign prod = block.settings.product %} {% if prod != blank %}
{{ prod.title }}

{{ prod.title }}

{{ prod.price | money }}

{% endif %} {% endfor %}
Total Items: 0
{% schema %} { "name": "Multi-Product Bundle", "settings": [ { "type": "text", "id": "heading", "label": "Heading", "default": "Build Your Bundle" } ], "blocks": [ { "type": "product", "name": "Product", "settings": [ { "type": "product", "id": "product", "label": "Select Product" } ] } ], "presets": [ { "name": "Multi-Product Bundle" } ] } {% endschema %}

And here’s the crucial JavaScript logic that goes with it (you can place this in your theme’s main JavaScript file or directly within the section’s script tags):

document.addEventListener('DOMContentLoaded', () => {
const c
if (!container) return;
const checkoutBtn = document.getElementById('bundle-checkout-btn');
const totalQtyDisplay = document.getElementById('total-bundle-qty');
const items = container.querySelectorAll('.bundle-item');
function updateTotal() {
  let total = 0;
  items.forEach(item => {
    const input = item.querySelector('.bundle-qty-input');
    total += parseInt(input.value || 0, 10);
  });
  totalQtyDisplay.textC
  checkoutBtn.disabled = total === 0;
}
// Handle + / - buttons
container.addEventListener('click', (e) => {
  if (e.target.classList.contains('qty-btn')) {
    const input = e.target.parentElement.querySelector('.bundle-qty-input');
    let val = parseInt(input.value || 0, 10);
    if (e.target.classList.contains('plus')) val++;
    if (e.target.classList.contains('minus') && val > 0) val--;
    input.value = val;
    updateTotal();
  }
});
// Handle direct input change
container.addEventListener('input', (e) => {
  if (e.target.classList.contains('bundle-qty-input')) {
    updateTotal();
  }
});
// Add all to cart and redirect straight to Checkout
checkoutBtn.addEventListener('click', async () => {
  const itemsToAdd = [];
  items.forEach(item => {
    const variantId = item.dataset.variantId;
    const qty = parseInt(item.querySelector('.bundle-qty-input').value || 0, 10);
    if (qty > 0 && variantId) {
      itemsToAdd.push({ id: parseInt(variantId, 10), quantity: qty });
    }
  });
  if (itemsToAdd.length === 0) return;
  checkoutBtn.innerText = 'Processing...';
  checkoutBtn.disabled = true;
  try {
    const resp fetch(window.Shopify.routes.root + 'cart/add.js', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ items: itemsToAdd })
    });
    if (response.ok) {
      // Redirect directly to checkout
      window.location.href = '/checkout';
    } else {
      const err = await response.json();
      alert('Error adding products: ' + (err.description || 'Please check stock availability.'));
      checkoutBtn.innerText = 'Buy Now';
      checkoutBtn.disabled = false;
    }
  } catch (error) {
    console.error(error);
    window.location.href = '/checkout';
  }
});
});

Implementing the Multi-Product Bundle Section:

The steps are similar to the previous section:

  1. Follow steps 1-4 from the previous section to create a new file in your sections folder. Name it multi-product-bundle.liquid.
  2. Paste the HTML/Liquid/Schema code block (the first one for this option) into this new file and click Save.
  3. Paste the JavaScript code block (the second one for this option) into your theme’s main JavaScript file (e.g., assets/theme.js or assets/global.js, depending on your theme structure) or within