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:
- Settings > Files - Upload files and get the CDN URL
- Product images - Use an existing product image URL
- 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
_componentsis 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
_discountproperty 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