Cart API Integration

Flex bundles use Shopify's Cart API to add bundles to the cart with line item properties. This guide covers both pricing modes: Parent Price (bundle uses the parent variant's price) and Component Sum (bundle price is calculated from individual component prices).

Overview

All Flex bundles are added to the cart using the /cart/add.js endpoint with a POST request. The bundle configuration is passed through line item properties, specifically the _components property which contains a JSON-stringified array of component objects.

Pricing Modes

Parent Price Mode

In this mode, the bundle uses the parent variant's price. You can optionally apply a discount percentage using the _discount property.

Use case: Fixed-price bundles where the components are pre-selected.

Component Sum Mode

In this mode, the bundle price is calculated by summing up the individual component prices. Each component must include a price property.

Use case: Build-your-own bundles, customizable bundles, or bundles where the price varies based on selections.


Parent Price Mode

When using the parent's price, add the bundle with the parent variant ID and include an optional _discount percentage.

Request Structure

let formData = {
  "items": [
    {
      "id": 51618980823341, // Parent variant ID
      "quantity": 1,
      "properties": {
        "_discount": 15, // OPTIONAL: Discount percentage (e.g., 15 = 15% off)
        "_components": JSON.stringify([
          {
            "id": 12345678901,      // Component variant ID
            "quantity": 2,          // Component quantity
            "attributes": {         // OPTIONAL: Component-specific attributes
              "Color": "Blue",
              "Size": "Medium",
              "Gift Message": "Happy Birthday!"
            }
          },
          {
            "id": 12345678902,
            "quantity": 1,
            "attributes": {
              "Style": "Classic",
              "Engraving": "Your text here"
            }
          },
          {
            "id": 12345678903,
            "quantity": 1
            // No attributes - component with defaults
          }
        ]),
        // Display properties (visible to customer)
        "Item 1": "Sample Gift with Gift Message",
        "Item 2": "Sample Monogrammed Gift",
        "Item 3": "Sample Gift",
        // OPTIONAL: Bundle settings
        "_settings": JSON.stringify({
          "title": "Sample Bundle",  // Custom bundle title
          "image": "https://cdn.shopify.com/s/files/1/0766/2665/7581/files/theme_cover_image.jpg?v=1684349973"
        })
      }
    }
  ]
};

Full Example

let formData = {
  "items": [
    {
      "id": 51618980823341,
      "quantity": 1,
      "properties": {
        "_discount": 15,
        "_components": JSON.stringify([
          {
            "id": 12345678901,
            "quantity": 2,
            "attributes": {
              "Color": "Blue",
              "Size": "Medium",
              "Gift Message": "Happy Birthday!"
            }
          },
          {
            "id": 12345678902,
            "quantity": 1,
            "attributes": {
              "Style": "Classic",
              "Engraving": "Your text here"
            }
          },
          {
            "id": 12345678903,
            "quantity": 1
          }
        ]),
        "Item 1": "Sample Gift with Gift Message",
        "Item 2": "Sample Monogrammed Gift",
        "Item 3": "Sample Gift",
        "_settings": JSON.stringify({
          "title": "Sample Bundle",
          "image": "https://cdn.shopify.com/s/files/1/0766/2665/7581/files/theme_cover_image.jpg?v=1684349973"
        })
      }
    }
  ]
};

fetch(window.Shopify.routes.root + 'cart/add.js', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(formData)
})
.then(response => response.json())
.then(data => {
  console.log('Bundle added:', data);
})
.catch((error) => {
  console.error('Error:', error);
});

Component Sum Mode

When summing component prices, each component must include a price property. The total bundle price is calculated from the sum of all component prices multiplied by their quantities.

Request Structure

let formData = {
  "items": [
    {
      "id": 51618980823341, // Parent variant ID
      "quantity": 1,
      "properties": {
        "_components": JSON.stringify([
          {
            "id": 12345678901,      // Component variant ID
            "quantity": 2,          // Component quantity
            "price": 49.99,         // Component price (REQUIRED for sum mode)
            "attributes": {         // OPTIONAL: Component-specific attributes
              "Color": "Blue",
              "Size": "Medium",
              "Gift Message": "Happy Birthday!"
            }
          },
          {
            "id": 12345678902,
            "quantity": 1,
            "price": 29.99,
            "attributes": {
              "Style": "Classic",
              "Engraving": "Your text here"
            }
          },
          {
            "id": 12345678903,
            "quantity": 1,
            "price": 19.99
            // No attributes - component with defaults
          }
        ]),
        // Display properties (visible to customer)
        "Item 1": "Sample Gift with Gift Message",
        "Item 2": "Sample Monogrammed Gift",
        "Item 3": "Sample Gift",
        // OPTIONAL: Bundle settings
        "_settings": JSON.stringify({
          "title": "Sample Bundle",
          "image": "https://cdn.shopify.com/s/files/1/0766/2665/7581/files/theme_cover_image.jpg?v=1684349973"
        })
      }
    }
  ]
};

Full Example

let formData = {
  "items": [
    {
      "id": 51618980823341,
      "quantity": 1,
      "properties": {
        "_components": JSON.stringify([
          {
            "id": 12345678901,
            "quantity": 2,
            "price": 49.99,
            "attributes": {
              "Color": "Blue",
              "Size": "Medium",
              "Gift Message": "Happy Birthday!"
            }
          },
          {
            "id": 12345678902,
            "quantity": 1,
            "price": 29.99,
            "attributes": {
              "Style": "Classic",
              "Engraving": "Your text here"
            }
          },
          {
            "id": 12345678903,
            "quantity": 1,
            "price": 19.99
          }
        ]),
        "Item 1": "Sample Gift with Gift Message",
        "Item 2": "Sample Monogrammed Gift",
        "Item 3": "Sample Gift",
        "_settings": JSON.stringify({
          "title": "Sample Bundle",
          "image": "https://cdn.shopify.com/s/files/1/0766/2665/7581/files/theme_cover_image.jpg?v=1684349973"
        })
      }
    }
  ]
};

fetch(window.Shopify.routes.root + 'cart/add.js', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(formData)
})
.then(response => response.json())
.then(data => {
  console.log('Bundle added:', data);
  // Total price: (49.99 * 2) + 29.99 + 19.99 = $149.96
})
.catch((error) => {
  console.error('Error:', error);
});

Property Reference

Line Item Properties

Property Type Required Description
_components String (JSON) Yes JSON-stringified array of component objects
_discount Number No Discount percentage (Parent Price mode only). E.g., 15 for 15% off
_settings String (JSON) No JSON-stringified settings object for bundle customization
Custom properties String No Any other properties will be displayed to the customer (e.g., "Item 1": "Gift Box")

Component Object

Property Type Required Description
id Number Yes Shopify variant ID of the component product
quantity Number Yes Quantity of this component in the bundle
price Number Component Sum only Price of the component (required for Component Sum mode)
attributes Object No Key-value pairs for component-specific attributes (customizations, options, etc.)

Settings Object

Property Type Required Description
title String No Custom title to display for the bundle in cart
image String No URL to a custom bundle image. Must be hosted on Shopify CDN

Understanding Properties

Hidden vs. Visible Properties

Properties that start with an underscore (_) are hidden from the customer in the cart and checkout. These are used for bundle configuration:

  • _components - Bundle component data
  • _discount - Discount percentage
  • _settings - Bundle display settings

Properties without an underscore are visible to the customer:

  • "Item 1": "Blue T-Shirt (Size M)" - Shown in cart
  • "Gift Message": "Happy Birthday!" - Shown in cart

Component Attributes

The attributes object within each component allows you to pass customization data that's specific to that component. This is useful for:

  • Product options (Color, Size, Material)
  • Personalization (Engraving, Monogram, Gift Message)
  • Custom configurations
{
  "id": 12345678901,
  "quantity": 1,
  "attributes": {
    "Color": "Navy Blue",
    "Size": "Large",
    "Monogram": "JKS",
    "Gift Wrap": "Yes"
  }
}

Image Requirements

When using a custom bundle image in _settings, the image must be hosted on Shopify's CDN. You can upload images to your Shopify store via:

  1. Settings > Files - Upload files and get the CDN URL
  2. Product images - Use an existing product image URL
  3. Theme assets - Reference theme asset URLs

Valid CDN URL format:

https://cdn.shopify.com/s/files/1/XXXX/XXXX/XXXX/files/your-image.jpg

Error Handling

Always implement proper error handling when adding bundles to the cart:

fetch(window.Shopify.routes.root + 'cart/add.js', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(formData)
})
.then(response => {
  if (!response.ok) {
    throw new Error('Network response was not ok');
  }
  return response.json();
})
.then(data => {
  if (data.status === 422) {
    // Handle validation errors
    console.error('Validation error:', data.description);
  } else {
    // Success
    console.log('Bundle added successfully:', data);
    // Optionally refresh cart or update UI
  }
})
.catch((error) => {
  console.error('Error adding bundle to cart:', error);
});

Common Issues

Bundle not processing correctly

  • Ensure _components is properly JSON-stringified
  • Verify all component variant IDs are valid and in stock
  • Check that the parent variant ID exists and is set up as a Flex Bundle

Discount not applying

  • The _discount property only works in Parent Price mode
  • Ensure the value is a number (not a string)
  • Discount is a percentage, not a fixed amount

Custom image not showing

  • Image must be hosted on Shopify's CDN
  • Verify the URL is publicly accessible
  • Check for typos in the URL