Articles on: Installation

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

  1. BSS_PO is undefined: The app hasn't loaded yet. Make sure OPTIS is installed and enabled in the theme app extension.
  2. 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.
  3. Price calculations seem incorrect: Remember that prices in the API are already multiplied by quantities. Use unitPrice if you need the per-unit cost.
  4. Quantity not updating: Quantity changes trigger the same events as other option changes. Listen for BSSOptionSelectionChanged events to detect quantity modifications.
  5. Validation not working with quantities: Validation checks if options are selected, not their quantities. Implement custom quantity validation based on your business rules.
  6. 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

  1. Always check for quantity data: Not all option types support quantities. Default to 1 if quantity data is unavailable.
  2. Use unit prices for custom calculations: When implementing custom pricing logic, use unitPrice and multiply by quantity yourself.
  3. Listen to events for real-time updates: Don't poll for changes; use the provided event system for better performance.
  4. Cache expensive calculations: If you're doing complex quantity-based calculations, cache results and only recalculate when the BSSOptionSelectionChanged event fires.
  5. 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

Was this article helpful?

Share your feedback

Cancel

Thank you!