> For the complete documentation index, see [llms.txt](https://docs.optis.me/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.optis.me/product-options/advanced-features/optis-product-options-developer-integration-guide.md).

# OPTIS Product Options - Developer Integration Guide

### Overview <a href="#id-2-overview" id="id-2-overview"></a>

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 <a href="#id-2-data-structure" id="id-2-data-structure"></a>

#### BSS\_PO.currentSelectedOptions <a href="#id-3-bsspocurrentselectedoptions" id="id-3-bsspocurrentselectedoptions"></a>

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 <a href="#id-3-new-quantity-data-structure" id="id-3-new-quantity-data-structure"></a>

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 <a href="#id-2-enhanced-option-value-structure" id="id-2-enhanced-option-value-structure"></a>

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 <a href="#id-2-api-methods" id="id-2-api-methods"></a>

We provide a comprehensive API through `BSS_PO.getSelectedOptions` for easy data access:

#### Basic Data Access <a href="#id-3-basic-data-access" id="id-3-basic-data-access"></a>

**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 <a href="#id-3-new-quantity-methods" id="id-3-new-quantity-methods"></a>

**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 <a href="#id-3-enhanced-price-information" id="id-3-enhanced-price-information"></a>

**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 <a href="#id-3-validation" id="id-3-validation"></a>

**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 <a href="#id-3-enhanced-formatted-output" id="id-3-enhanced-formatted-output"></a>

**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 <a href="#id-2-new-complete-api-methods-for-quantity-support" id="id-2-new-complete-api-methods-for-quantity-support"></a>

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 <a href="#id-2-event-system" id="id-2-event-system"></a>

#### Listen for Option Changes with Quantity Information <a href="#id-3-listen-for-option-changes-with-quantity-information" id="id-3-listen-for-option-changes-with-quantity-information"></a>

```
// 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 <a href="#id-2-cart-page-integration" id="id-2-cart-page-integration"></a>

#### Detecting Option Changes on Cart Page <a href="#id-3-detecting-option-changes-on-cart-page" id="id-3-detecting-option-changes-on-cart-page"></a>

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 <a href="#id-3-enhanced-cart-data-structure" id="id-3-enhanced-cart-data-structure"></a>

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 <a href="#id-2-practical-examples" id="id-2-practical-examples"></a>

#### Example 1: Quantity-Aware Price Calculator <a href="#id-3-example-1-quantity-aware-price-calculator" id="id-3-example-1-quantity-aware-price-calculator"></a>

```
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 <a href="#id-3-example-2-inventory-management-integration" id="id-3-example-2-inventory-management-integration"></a>

```
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 <a href="#id-3-example-3-dynamic-pricing-based-on-quantity" id="id-3-example-3-dynamic-pricing-based-on-quantity"></a>

```
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 <a href="#id-2-troubleshooting" id="id-2-troubleshooting"></a>

#### Common Issues <a href="#id-3-common-issues" id="id-3-common-issues"></a>

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 <a href="#id-3-best-practices" id="id-3-best-practices"></a>

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.)
