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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.optis.me/product-options/advanced-features/optis-product-options-developer-integration-guide.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
