OPTIS Product Options - Developer Integration Guide
Overview
The OPTIS Product Options app exposes a global JavaScript object BSS_PO.currentSelectedOptions
that contains real-time information about customer-selected product options, including prices, validation status, quantities, and detailed breakdowns. This guide explains how third-party developers can integrate with and read data from our app.
Data Structure
BSS_PO.currentSelectedOptions
The main data object contains the following structure:
BSS_PO.currentSelectedOptions = {
byOptionId: {}, // Options indexed by option ID
byLabel: {}, // Options indexed by label name
byOptionSetId: {}, // Options grouped by option set ID
allValues: [], // Flat array of all selected options
quantities: {}, // NEW: Quantity data for each option value
totalPriceAddOn: 0, // Total price add-on from all options
totalVariantPriceAddOn: 0, // Total variant-based price add-on
variantPriceAddOn: [], // Array of variant price add-ons
priceBreakdown: {}, // Detailed price breakdown by option
validation: { // Validation status for required options
isValid: true,
requiredOptions: [],
missingOptions: [],
validOptions: [],
errors: []
},
timestamp: "2023-..." // Last update timestamp
}
NEW: Quantity Data Structure
The quantities
object stores quantity information for each option value:
quantities: {
// By option label and value combination
"Size_Large": 2,
"Color_Red": 1,
// By option ID (for easy lookup)
"optionId_123": {
"Large": 2,
"Medium": 1
},
"optionId_124": {
"Red": 1,
"Blue": 3
}
}
Enhanced Option Value Structure
Each option value now includes quantity and pricing information:
{
value: "Large",
label: "Size",
index: 0,
quantity: 2, // NEW: Selected quantity for this value
unitPrice: 5.00, // NEW: Price per unit
unitVariantPrice: 0, // NEW: Variant price per unit
price: 10.00, // Total price (unitPrice × quantity)
variantPrice: 0, // Total variant price (unitVariantPrice × quantity)
id: 123
}
API Methods
We provide a comprehensive API through BSS_PO.getSelectedOptions
for easy data access:
Basic Data Access
Get All Selected Options
// Get all selected options with price and quantity information
const allOptions = BSS_PO.getSelectedOptions.all();
console.log(allOptions);
// Returns: [{
// label: "Size",
// value: "Large",
// quantity: 2,
// unitPrice: 5.00,
// price: 10.00,
// variantPrice: 0,
// id: 123,
// index: 0
// }, ...]
Get Options by Label
// Get all values for a specific option (useful for multi-select options)
const colorOptions = BSS_PO.getSelectedOptions.byLabel("Color");
console.log(colorOptions);
// Returns: [{
// value: "Red",
// quantity: 1,
// unitPrice: 2.00,
// price: 2.00,
// variantPrice: 0,
// id: 124,
// index: 0
// }]
// Get single value for radio/dropdown options
const selectedSize = BSS_PO.getSelectedOptions.getValue("Size");
console.log(selectedSize); // "Large"
// Get multiple values for checkbox options
const selectedFeatures = BSS_PO.getSelectedOptions.getValues("Features");
console.log(selectedFeatures); // ["Waterproof", "Extended Warranty"]
Check Option Selection
// Check if a specific option value is selected
const isRedSelected = BSS_PO.getSelectedOptions.isSelected("Color", "Red");
console.log(isRedSelected); // true
NEW: Quantity Methods
Get Quantity Information
// Get quantity for a specific option value
const sizeQuantity = BSS_PO.getSelectedOptions.getQuantity("Size", "Large");
console.log(`Large size quantity: ${sizeQuantity}`); // 2
// Get all quantities for an option
const sizeQuantities = BSS_PO.getSelectedOptions.getQuantities("Size");
console.log(sizeQuantities); // { "Large": 2, "Medium": 1 }
// Get quantity by option ID
const optionQuantities = BSS_PO.getSelectedOptions.getQuantitiesByOptionId(123);
console.log(optionQuantities); // { "Large": 2, "Medium": 1 }
// Get total quantity across all options
const totalQuantity = BSS_PO.getSelectedOptions.getTotalQuantity();
console.log(`Total items: ${totalQuantity}`); // 4
Enhanced Price Information
Get Price Data with Quantity
// Get total price add-on from all options (includes quantity multiplication)
const totalPrice = BSS_PO.getSelectedOptions.getTotalPriceAddOn();
console.log(`Total extra: $${totalPrice.toFixed(2)}`);
// Get unit price vs total price for specific option value
const unitPrice = BSS_PO.getSelectedOptions.getUnitPriceByValue("Size", "Large");
const totalPrice = BSS_PO.getSelectedOptions.getPriceByValue("Size", "Large");
const quantity = BSS_PO.getSelectedOptions.getQuantity("Size", "Large");
console.log(`Unit price: $${unitPrice}, Quantity: ${quantity}, Total: $${totalPrice}`);
// Get price breakdown with quantity details
const breakdown = BSS_PO.getSelectedOptions.getPriceBreakdownWithQuantities();
console.log(breakdown);
/* Returns:
{
"Size": {
values: [{
value: "Large",
quantity: 2,
unitPrice: 5.00,
unitVariantPrice: 0,
price: 10.00,
variantPrice: 0,
extraPriceType: 0
}],
totalPrice: 10.00,
totalVariantPrice: 0,
totalQuantity: 2
}
}
*/
Get Comprehensive Price Summary with Quantities
const priceSummary = BSS_PO.getSelectedOptions.getPriceSummary();
console.log(priceSummary);
/* Returns:
{
total: 15.00,
totalVariant: 5.00,
grandTotal: 20.00,
totalQuantity: 3, // NEW: Total quantity across all options
formattedTotal: "15.00",
formattedVariantTotal: "5.00",
formattedGrandTotal: "20.00",
details: [
{
label: "Size",
price: 10.00,
variantPrice: 0,
quantity: 2, // NEW: Total quantity for this option
unitPrice: 5.00, // NEW: Average unit price
formattedPrice: "10.00",
values: [{
value: "Large",
quantity: 2, // NEW: Quantity for this specific value
unitPrice: 5.00, // NEW: Unit price for this value
price: 10.00,
variantPrice: 0
}]
}
]
}
*/
Validation
Check Validation Status
// Check if all required options are selected
const isValid = BSS_PO.getSelectedOptions.isValid();
console.log(`Form valid: ${isValid}`);
// Get validation summary
const validation = BSS_PO.getSelectedOptions.getValidationSummary();
console.log(validation);
/* Returns:
{
isValid: false,
totalRequired: 3,
totalMissing: 1,
totalValid: 2,
message: "1 required option(s) need to be selected",
missingLabels: ["Color"],
errors: ["Color is required and must be selected."],
details: { required: [...], missing: [...], valid: [...] }
}
*/
// Check specific option requirements
const isColorRequired = BSS_PO.getSelectedOptions.isRequired("Color");
const isColorMissing = BSS_PO.getSelectedOptions.isMissing("Color");
Get Validation Errors
const errors = BSS_PO.getSelectedOptions.getValidationErrors();
errors.forEach(error => console.error(error));
Enhanced Formatted Output
Get Formatted Strings with Quantities
// Get formatted string of all selections with prices and quantities
const formatted = BSS_PO.getSelectedOptions.getFormattedSelectionsWithPrices();
console.log(formatted);
// "Size: Large (×2, +10.00) | Color: Red (×1, +2.00) | Features: Waterproof, Extended Warranty (×1, +5.00)"
// Get formatted string with detailed quantity breakdown
const detailedFormat = BSS_PO.getSelectedOptions.getFormattedSelectionsDetailed();
console.log(detailedFormat);
// "Size: Large (2 units @ $5.00 each = $10.00) | Color: Red (1 unit @ $2.00 = $2.00)"
NEW: Complete API Methods for Quantity Support
Here are the additional methods added to support quantity functionality:
BSS_PO.getSelectedOptions = {
// ... existing methods ...
// === QUANTITY METHODS ===
// Get quantity for specific option value
getQuantity: (label, value) => {
const quantityKey = `${label}_${value}`;
return BSS_PO.currentSelectedOptions?.quantities[quantityKey] || 0;
},
// Get all quantities for an option label
getQuantities: (label) => {
const options = BSS_PO.currentSelectedOptions?.byLabel[label] || [];
const quantities = {};
options.forEach(option => {
quantities[option.value] = option.quantity || 1;
});
return quantities;
},
// Get quantities by option ID
getQuantitiesByOptionId: (optionId) => {
return BSS_PO.currentSelectedOptions?.quantities[`optionId_${optionId}`] || {};
},
// Get total quantity across all options
getTotalQuantity: () => {
const allValues = BSS_PO.currentSelectedOptions?.allValues || [];
return allValues.reduce((total, option) => total + (option.quantity || 1), 0);
},
// === ENHANCED PRICE METHODS WITH QUANTITY ===
// Get unit price for specific option value (before quantity multiplication)
getUnitPriceByValue: (label, value) => {
const breakdown = BSS_PO.currentSelectedOptions?.priceBreakdown[label];
if (breakdown && breakdown.values) {
const valueObj = breakdown.values.find(v => v.value === value);
return valueObj ? (valueObj.unitPrice || 0) : 0;
}
return 0;
},
// Get unit variant price for specific option value
getUnitVariantPriceByValue: (label, value) => {
const breakdown = BSS_PO.currentSelectedOptions?.priceBreakdown[label];
if (breakdown && breakdown.values) {
const valueObj = breakdown.values.find(v => v.value === value);
return valueObj ? (valueObj.unitVariantPrice || 0) : 0;
}
return 0;
},
// Get price breakdown including quantity details
getPriceBreakdownWithQuantities: () => {
const breakdown = BSS_PO.currentSelectedOptions?.priceBreakdown || {};
const enhanced = {};
Object.entries(breakdown).forEach(([label, data]) => {
enhanced[label] = {
...data,
totalQuantity: data.values.reduce((sum, v) => sum + (v.quantity || 1), 0),
averageUnitPrice: data.values.length > 0 ?
data.values.reduce((sum, v) => sum + (v.unitPrice || 0), 0) / data.values.length : 0
};
});
return enhanced;
},
// === ENHANCED FORMATTED OUTPUT ===
// Get formatted selections with quantity details
getFormattedSelectionsDetailed: () => {
const allValues = BSS_PO.currentSelectedOptions?.allValues || [];
const grouped = {};
allValues.forEach(option => {
if (!grouped[option.label]) {
grouped[option.label] = [];
}
grouped[option.label].push({
value: option.value,
quantity: option.quantity || 1,
unitPrice: option.unitPrice || 0,
price: option.price || 0
});
});
return Object.entries(grouped).map(([label, items]) => {
const values = items.map(item => {
if (item.quantity > 1) {
return `${item.value} (${item.quantity} units @ $${item.unitPrice.toFixed(2)} each = $${item.price.toFixed(2)})`;
} else {
return item.price > 0 ?
`${item.value} (+$${item.price.toFixed(2)})` :
item.value;
}
}).join(', ');
return `${label}: ${values}`;
}).join(' | ');
}
};
Event System
Listen for Option Changes with Quantity Information
// Listen for any option selection changes
document.addEventListener('BSSOptionSelectionChanged', (event) => {
const {
selectedOptions,
quantities, // NEW: Quantity data
totalPriceAddOn,
priceBreakdown
} = event.detail;
console.log('Options changed:', selectedOptions);
console.log('Quantities:', quantities);
console.log('New total price:', totalPriceAddOn);
console.log('Price breakdown:', priceBreakdown);
// Update your UI with quantity information
updateCartSummary(totalPriceAddOn, quantities);
});
// Listen for validation failures
document.addEventListener('BSSOptionValidationFailed', (event) => {
const { validation, missingOptions, errors } = event.detail;
console.log('Validation failed:', validation);
console.log('Missing options:', missingOptions);
// Show validation errors to user
displayValidationErrors(errors);
});
// Listen for price changes (includes quantity-based price calculations)
document.addEventListener('BSSchangeProductPrice', (event) => {
console.log('Product price changed, recalculate total with quantities');
// Update your price display with quantity considerations
const newTotal = BSS_PO.getSelectedOptions.getTotalPriceAddOn();
const totalQuantity = BSS_PO.getSelectedOptions.getTotalQuantity();
updatePriceDisplay(newTotal, totalQuantity);
});
Cart Page Integration
Detecting Option Changes on Cart Page
On the cart page, customers can edit their product options and quantities. To detect these changes:
// Listen for cart changes (additions, removals, option edits, quantity changes)
window.addEventListener('cart_changed', (event) => {
const { newCart, oldCart, changedCartItems } = event.detail;
console.log('Cart changed:', {
newCart,
oldCart,
changedItems: changedCartItems
});
// Fetch updated cart data to get latest option and quantity information
fetchUpdatedCartData();
});
// Function to fetch and process updated cart data with quantity information
async function fetchUpdatedCartData() {
try {
const response = await fetch('/cart.js');
const cartData = await response.json();
// Process cart items to extract option and quantity information
cartData.items.forEach(item => {
// Check for OPTIS option properties
const optionProperties = {};
// Extract option data from cart item properties
Object.keys(item.properties || {}).forEach(key => {
// OPTIS stores option data in properties with specific patterns
if (key.includes('_bssPrice') || key.includes('_bssVariants') ||
key.includes('_bssCustomAttributes') || key.includes('_bssQuantities')) {
try {
optionProperties[key] = JSON.parse(item.properties[key]);
} catch (e) {
optionProperties[key] = item.properties[key];
}
}
});
console.log(`Item ${item.key} options:`, optionProperties);
console.log(`Item ${item.key} cart quantity:`, item.quantity);
// Update your UI based on the new option and quantity data
updateCartItemDisplay(item.key, optionProperties, item.quantity);
});
} catch (error) {
console.error('Error fetching cart data:', error);
}
}
// Example: Update cart item display with quantity information
function updateCartItemDisplay(itemKey, optionData, cartQuantity) {
const cartItemElement = document.querySelector(`[data-cart-item="${itemKey}"]`);
if (cartItemElement && optionData._bssPrice) {
const extraPrice = optionData._bssPrice.extra || 0;
const quantities = optionData._bssQuantities || {};
// Update price display with quantity considerations
const priceElement = cartItemElement.querySelector('.item-price');
if (priceElement) {
console.log(`Item ${itemKey} has extra price: $${extraPrice}`);
console.log(`Item ${itemKey} option quantities:`, quantities);
console.log(`Item ${itemKey} cart quantity:`, cartQuantity);
}
}
}
Enhanced Cart Data Structure
Cart items now include quantity information for options:
// Example cart item with OPTIS option and quantity data
{
"key": "39772975595588:abc123",
"id": 39772975595588,
"properties": {
"Size": "Large (×2)",
"Color": "Red (+$5.00)",
"_bssPrice": "{\"extra\":15.00}",
"_bssQuantities": "{\"Size_Large\":2,\"Color_Red\":1}", // NEW: Quantity data
"_bssVariants": "{\"Size\":{\"id\":\"39772975595588\",\"quantity\":2}}",
"_bssCustomAttributes": "{\"Size\":\"Large\",\"Color\":\"Red\"}"
},
"quantity": 1, // This is the cart line item quantity
"variant_id": 39772975595588
}
Practical Examples
Example 1: Quantity-Aware Price Calculator
function calculateOptionPricing() {
const allOptions = BSS_PO.getSelectedOptions.all();
let totalCost = 0;
let breakdown = [];
allOptions.forEach(option => {
const itemCost = option.price; // Already includes quantity multiplication
totalCost += itemCost;
breakdown.push({
option: option.label,
value: option.value,
quantity: option.quantity,
unitPrice: option.unitPrice,
totalPrice: itemCost,
formatted: option.quantity > 1 ?
`${option.value} (${option.quantity}× $${option.unitPrice.toFixed(2)} = $${itemCost.toFixed(2)})` :
`${option.value} (+$${itemCost.toFixed(2)})`
});
});
return { totalCost, breakdown };
}
Example 2: Inventory Management Integration
function checkInventoryAvailability() {
const selectedOptions = BSS_PO.getSelectedOptions.all();
const inventoryWarnings = [];
selectedOptions.forEach(option => {
// Check if requested quantity exceeds available inventory
const availableStock = getInventoryForOption(option.label, option.value);
if (option.quantity > availableStock) {
inventoryWarnings.push({
option: `${option.label}: ${option.value}`,
requested: option.quantity,
available: availableStock,
message: `Only ${availableStock} units available for ${option.label}: ${option.value}`
});
}
});
return inventoryWarnings;
}
Example 3: Dynamic Pricing Based on Quantity
function applyQuantityDiscounts() {
const options = BSS_PO.getSelectedOptions.all();
let adjustedTotal = 0;
options.forEach(option => {
let finalPrice = option.price;
// Apply quantity-based discounts
if (option.quantity >= 10) {
finalPrice *= 0.9; // 10% discount for 10+ items
} else if (option.quantity >= 5) {
finalPrice *= 0.95; // 5% discount for 5+ items
}
adjustedTotal += finalPrice;
console.log(`${option.label}: ${option.value} - ${option.quantity} units`);
console.log(`Original: $${option.price.toFixed(2)}, Discounted: $${finalPrice.toFixed(2)}`);
});
return adjustedTotal;
}
Troubleshooting
Common Issues
- BSS_PO is undefined: The app hasn't loaded yet. Make sure OPTIS is installed and enabled in the theme app extension.
- Quantity data is missing: Ensure you're using the updated version of OPTIS that supports quantity tracking. Check that quantity inputs are properly configured for your option types.
- Price calculations seem incorrect: Remember that prices in the API are already multiplied by quantities. Use
unitPrice
if you need the per-unit cost. - Quantity not updating: Quantity changes trigger the same events as other option changes. Listen for
BSSOptionSelectionChanged
events to detect quantity modifications. - Validation not working with quantities: Validation checks if options are selected, not their quantities. Implement custom quantity validation based on your business rules.
- Cart quantities vs option quantities: Cart line item quantity is different from individual option quantities. A single cart item can have multiple option values with different quantities each.
Best Practices
- Always check for quantity data: Not all option types support quantities. Default to 1 if quantity data is unavailable.
- Use unit prices for custom calculations: When implementing custom pricing logic, use
unitPrice
and multiply byquantity
yourself. - Listen to events for real-time updates: Don't poll for changes; use the provided event system for better performance.
- Cache expensive calculations: If you're doing complex quantity-based calculations, cache results and only recalculate when the
BSSOptionSelectionChanged
event fires. - Validate quantities in your code: Implement business logic to ensure quantities make sense for your use case (minimum/maximum quantities, inventory checks, etc.).
Updated on: 03/09/2025
Thank you!