npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@liquidcommerce/elements-sdk

v2.6.4

Published

LiquidCommerce Elements SDK

Readme

JavaScript TypeScript npm pnpm Rollup

License: UNLICENSED Bundle Size Zero Dependencies Browser Support

Add product, cart, and checkout experiences to any website with a few lines of code

📋 Table of Contents

🎯 Overview

The LiquidCommerce Elements SDK is a production-ready JavaScript library that enables partners to seamlessly integrate product displays, shopping carts, and checkout flows into any website. Built with performance and developer experience in mind.

✨ Key Features

🚀 Quick Integration

  • Auto-initialization with data attributes
  • Zero configuration setup
  • CDN or NPM installation
  • Works with any framework or vanilla JS

🛍️ Complete E-commerce

  • Product display components
  • Shopping cart with real-time updates
  • Full checkout flow
  • Address management

🎨 Customizable UI

  • Comprehensive theme system
  • Component-level styling
  • Responsive design
  • Modern, accessible components

⚡ Performance First

  • ~150KB bundle size
  • Zero runtime dependencies
  • Lazy loading support
  • Optimized for Core Web Vitals

🚀 Quick Start

The fastest way to add e-commerce to your site is with auto-initialization - a single script tag that does everything.

The Simplest Setup (30 seconds)

Add this single script tag to your page:

<script
  data-liquid-commerce-elements
  data-token="YOUR_API_KEY"
  data-env="production"
  src="https://assets-elements.liquidcommerce.us/all/elements.js"
></script>

That's it! You now have:

  • ✅ A floating cart button (bottom right)
  • ✅ Full cart functionality
  • ✅ Complete checkout flow
  • ✅ Ready to add products

Adding Products to Your Page

Now let's display products. Choose the method that fits your use case:

Method 1: Direct Attributes (Best for static pages)

Add products directly in the script tag:

<div id="product-1"></div>
<div id="product-2"></div>

<script
  data-liquid-commerce-elements
  data-token="YOUR_API_KEY"
  data-env="production"
  data-container-1="product-1"
  data-product-1="00619947000020"
  data-container-2="product-2"
  data-product-2="00832889005513"
  src="https://assets-elements.liquidcommerce.us/all/elements.js"
></script>

Use case: Static HTML pages with known products

Method 2: JSON Configuration (Best for CMS/dynamic content)

Configure products via a JSON script block:

<div id="product-1"></div>
<div id="product-2"></div>

<script data-liquid-commerce-elements-products type="application/json">
[
  { "containerId": "product-1", "identifier": "00619947000020" },
  { "containerId": "product-2", "identifier": "00832889005513" }
]
</script>

<script
  data-liquid-commerce-elements
  data-token="YOUR_API_KEY"
  data-env="production"
  src="https://assets-elements.liquidcommerce.us/all/elements.js"
></script>

Use case: CMS platforms, templating engines, server-side rendering

Method 3: Annotated Elements (Best for grids/lists)

Mark any div with a data attribute:

<div class="product-grid">
  <div data-lce-product="00619947000020"></div>
  <div data-lce-product="00832889005513"></div>
  <div data-lce-product="00851468007252"></div>
</div>

<script
  data-liquid-commerce-elements
  data-token="YOUR_API_KEY"
  data-env="production"
  src="https://assets-elements.liquidcommerce.us/all/elements.js"
></script>

Use case: Product grids, category pages, search results

Adding a Product List

Display a filtered, paginated product catalog with infinite scroll. Perfect for category pages, catalog pages, and search results.

Add a product list to any page with a data attribute:

<div data-liquid-commerce-elements-products-list
     data-card="standard"
     data-rows="3"
     data-columns="4"
     data-filters="personalization,pre-order,delivery-options"
     data-product-url="/product/{upc}">
</div>

<script
  data-liquid-commerce-elements
  data-token="YOUR_API_KEY"
  data-env="production"
  src="https://assets-elements.liquidcommerce.us/all/elements.js"
></script>

Attributes:

  • data-liquid-commerce-elements-products-list - Enables product list on this div
  • data-card - Card variant: standard (default)
  • data-rows - Number of rows per page (default: 3)
  • data-columns - Number of columns (default: 4)
  • data-card-fill - Makes cards fill available space (optional flag)
  • data-filters - Comma-separated filters: personalization, pre-order, delivery-options
  • data-product-url - Product URL template with {upc} or {grouping} placeholder

Use case: Category pages, catalog pages, search results, filtered product browsing

Customizing the Cart Button

By default, you get a floating cart button with badge. Here's how to customize it:

Option 1: Cart Button in a Specific Container

<nav>
  <div id="header-cart"></div>
</nav>

<script
  data-liquid-commerce-elements
  data-token="YOUR_API_KEY"
  data-env="production"
  data-cart-badge-button="header-cart"
  src="https://assets-elements.liquidcommerce.us/all/elements.js"
></script>

Position Options:

You can control where the cart button is placed relative to a target element:

<!-- Place inside the target (default) -->
<script data-cart-badge-button="header-cart" ...></script>

<!-- Place above the target -->
<script data-cart-badge-button="above:.header-logo" ...></script>

<!-- Place below the target -->
<script data-cart-badge-button="below:#main-nav" ...></script>

<!-- Replace the target -->
<script data-cart-badge-button="replace:.old-cart" ...></script>

ID Auto-Prefixing:

Element IDs are automatically prefixed with # if needed:

<!-- These are equivalent: -->
<script data-cart-button="header-cart" ...></script>
<script data-cart-button="#header-cart" ...></script>

Option 2: Floating Cart Button (Default)

If no cart button attribute is provided, or if the target element is not found, the SDK automatically falls back to a floating cart button (bottom-right corner):

<!-- Empty attribute = floating cart button without badge -->
<script data-cart-button="" ...></script>

<!-- Empty badge attribute = floating cart button with badge -->
<script data-cart-badge-button="" ...></script>

<!-- Or simply omit the attribute for floating button with badge -->
<script
  data-liquid-commerce-elements
  data-token="YOUR_API_KEY"
  src="https://assets-elements.liquidcommerce.us/all/elements.js"
></script>

Option 3: No Cart Button (Manual Control)

Hide the cart button completely when you want to manage cart access manually:

<script
  data-liquid-commerce-elements
  data-token="YOUR_API_KEY"
  data-env="production"
  data-cart-button-hidden
  src="https://assets-elements.liquidcommerce.us/all/elements.js"
></script>

Use case: When you have a custom cart implementation or want to trigger cart display programmatically using client.actions.cart.openCart()

Advanced Auto-Init Features

Add Product via URL (Marketing Links)

Enable "add to cart" via URL parameters for email campaigns and ads:

<script
  data-liquid-commerce-elements
  data-token="YOUR_API_KEY"
  data-env="production"
  data-product-param="lce_product"
  data-product-fulfillment-type-param="lce_fulfillment"
  src="https://assets-elements.liquidcommerce.us/all/elements.js"
></script>

Now this URL auto-adds a product to cart:

https://yoursite.com/shop?lce_product=00619947000020&lce_fulfillment=shipping

Use case: Email campaigns, social media ads, QR codes

Apply Promo Code via URL (Campaign Tracking)

Auto-apply promo codes from URL parameters:

<script
  data-liquid-commerce-elements
  data-token="YOUR_API_KEY"
  data-env="production"
  data-promo-code-param="lce_promo"
  src="https://assets-elements.liquidcommerce.us/all/elements.js"
></script>

Now this URL auto-applies a promo code:

https://yoursite.com/shop?lce_promo=SUMMER20

Use case: Promotional campaigns, influencer codes, affiliate links

Promo Ticker (Rotating Promotions)

Display rotating promotional messages:

<script
  data-liquid-commerce-elements
  data-token="YOUR_API_KEY"
  data-env="production"
  data-promo-code="FREESHIP"
  data-promo-text="Free Shipping Today Only!|Use code FREESHIP at checkout"
  data-promo-separator="•"
  data-promo-active-from="2025-01-01T00:00:00Z"
  data-promo-active-until="2025-01-31T23:59:59Z"
  src="https://assets-elements.liquidcommerce.us/all/elements.js"
></script>

Use case: Time-sensitive promotions, holiday sales, flash deals

Debug Mode (Development)

Enable debug logging during development:

<script
  data-liquid-commerce-elements
  data-token="YOUR_API_KEY"
  data-env="development"
  data-debug-mode="console"
  src="https://assets-elements.liquidcommerce.us/all/elements.js"
></script>

Debug modes:

  • console - Logs to browser console
  • panel - Shows visual debug panel + console logs
  • Not set - No debugging (production default)

Complete Auto-Init Reference

Here's every available data attribute:

<script
  data-liquid-commerce-elements
  
  <!-- Required -->
  data-token="YOUR_API_KEY"
  
  <!-- Environment -->
  data-env="production|staging|development|local"
  
  <!-- Cart Button -->
  data-cart-button="container-id"              <!-- Simple cart button (no badge) -->
  data-cart-badge-button="container-id"        <!-- Cart button with badge -->
  data-cart-button-hidden                      <!-- Hide cart button completely -->
  
  <!-- Cart Button with Position Prefixes -->
  data-cart-button="above:.logo"               <!-- Place above target -->
  data-cart-badge-button="below:#header"       <!-- Place below target -->
  data-cart-button="inside:.nav"               <!-- Place inside target (default) -->
  data-cart-badge-button="replace:.old-cart"   <!-- Replace target -->
  
  <!-- Cart Button Floating (empty values) -->
  data-cart-button=""                          <!-- Floating button without badge -->
  data-cart-badge-button=""                    <!-- Floating button with badge -->
  
  <!-- Products (Method 1: Direct) -->
  data-container-1="div-id"
  data-product-1="identifier"
  data-container-2="div-id"
  data-product-2="identifier"
  
  <!-- Product List -->
  <div data-liquid-commerce-elements-products-list
       data-card="standard"
       data-rows="3"
       data-columns="4"
       data-card-fill
       data-filters="personalization,pre-order,delivery-options"
       data-product-url="/product/{upc}">
  </div>
  
  <!-- URL Parameters -->
  data-product-param="lce_product"
  data-product-fulfillment-type-param="lce_fulfillment"
  data-promo-code-param="lce_promo"
  
  <!-- Promo Ticker -->
  data-promo-code="CODE"
  data-promo-text="Message 1|Message 2"
  data-promo-separator="•"
  data-promo-active-from="2025-01-01T00:00:00Z"
  data-promo-active-until="2025-12-31T23:59:59Z"
  
  <!-- Debugging (dev only) -->
  data-debug-mode="console|panel"
  
  src="https://assets-elements.liquidcommerce.us/all/elements.js"
></script>

📖 For complete auto-init options: See docs/CONFIGURATION.md for all data attributes and configuration details.


🔧 Advanced Usage

Need more control? Initialize programmatically for full access to the SDK API.

Installation

CDN:

<script src="https://assets-elements.liquidcommerce.us/all/elements.js"></script>

<!-- Pin to specific version: -->
<script src="https://assets-elements.liquidcommerce.us/all/1.2.3/elements.js"></script>

NPM:

npm install @liquidcommerce/elements-sdk
# or
pnpm add @liquidcommerce/elements-sdk

Programmatic Initialization

<script src="https://assets-elements.liquidcommerce.us/all/elements.js"></script>
<script>
(async () => {
  const client = await window.Elements('YOUR_API_KEY', {
    env: 'production',
    debugMode: 'none',
    customTheme: { /* theming overrides */ },
    proxy: { /* proxy config */ }
  });

  // Inject components
  const components = await client.injectProductElement([
    { containerId: 'pdp-1', identifier: '00619947000020' }
  ]);

  // Create cart button
  client.ui.cartButton('cart-container', true);

  // Use actions API
  await client.actions.cart.addProduct([{
    identifier: '00619947000020',
    fulfillmentType: 'shipping',
    quantity: 1
  }]);
})();
</script>

NPM Import:

import { Elements } from '@liquidcommerce/elements-sdk';

const client = await Elements('YOUR_API_KEY', { env: 'production' });

🌐 Browser Support

⚠️ Important: This SDK is designed for browser environments only. It will not work in server-side rendering, Node.js, or other non-browser environments.

Supported Browsers (2018+)

| Browser | Minimum Version | Released | |---------|----------------|----------| | Chrome | 66+ | April 2018 | | Firefox | 60+ | May 2018 | | Safari | 12+ | September 2018 | | Edge | 79+ (Chromium) | January 2020 | | Samsung Internet | 7.2+ | June 2018 |

📖 See docs/BROWSER_SUPPORT.md for detailed compatibility.

⚙️ Configuration

Basic Configuration

const client = await Elements('YOUR_API_KEY', {
  env: 'production',           // Environment
  debugMode: 'none',           // Debug mode
  customTheme: { },            // Theme overrides
  proxy: { },                  // Proxy configuration
  promoTicker: [ ]             // Promotional messages
});

Environment Options

env: 'production'    // Live environment (default)
env: 'staging'       // Pre-production testing
env: 'development'   // Development with extra logging
env: 'local'         // Local development

Debug Modes

debugMode: 'none'     // No debugging (production default)
debugMode: 'console'  // Console logs only
debugMode: 'panel'    // Visual debug panel + console logs

Note: Debug mode is automatically disabled in production environment for security.

Custom Theme

Override default styles and layouts:

customTheme: {
  global: {
    theme: {
      primaryColor: '#007bff',
      accentColor: '#6c757d',
      successColor: '#28a745',
      errorColor: '#dc3545',
      buttonCornerRadius: '8px',
      cardCornerRadius: '12px',
      headingFont: {
        name: 'Inter',
        weights: [600, 700]
      },
      paragraphFont: {
        name: 'Inter',
        weights: [400, 500]
      }
    },
    layout: {
      allowPromoCodes: true,
      inputFieldStyle: 'outlined'
    }
  },
  product: {
    layout: {
      showDescription: true,
      addToCartButtonText: 'Add to Cart',
      fulfillmentDisplay: 'carousel'
    }
  },
  cart: {
    layout: {
      showQuantityCounter: true,
      drawerHeaderText: 'Your Cart'
    }
  },
  checkout: {
    layout: {
      allowGiftCards: true,
      emailOptIn: { show: true, checked: false, text: 'Email me with news' },
      smsOptIn: { show: true, checked: false, text: 'Text me with updates' }
    }
  }
}

📖 For complete theming options: See docs/THEMING.md

Proxy Configuration

Route API requests through your server to avoid ad blockers:

proxy: {
  baseUrl: 'https://yourdomain.com/api/proxy',
  headers: {
    'X-Custom-Auth': 'your-token'
  }
}

See docs/PROXY.md for implementation guide.

Promo Ticker

Display rotating promotional messages:

promoTicker: [
  {
    promoCode: 'FREESHIP',
    text: ['Free Shipping Today!', 'Use code FREESHIP'],
    separator: '•',
    activeFrom: '2025-01-01T00:00:00Z',
    activeUntil: '2025-12-31T23:59:59Z'
  }
]

📖 For all configuration options: See docs/CONFIGURATION.md for complete reference with TypeScript types.


📖 SDK Methods & API

Component Injection

Inject SDK components into your page containers. All injection methods return wrapper objects that provide component control.

Products

const components = await client.injectProductElement([
  { containerId: 'pdp-1', identifier: '00619947000020' },
  { containerId: 'pdp-2', identifier: '00832889005513' }
]);

// Returns: IInjectedComponent[] - Array of component wrappers
// components[0].rerender() - Rerender the first component
// components[0].getElement() - Get the container element
// components[0].getType() - Get component type

Identifier types: UPC, product ID, or Salsify grouping ID

Cart

const component = await client.injectCartElement('cart-container');

// Returns: IInjectedComponent | null - Component wrapper or null if failed
// component.rerender() - Rerender the component
// component.getElement() - Get the container element
// component.getType() - Get component type

Use case: Dedicated cart page

Checkout

const component = await client.injectCheckoutElement('checkout-container');

// Returns: IInjectedComponent | null - Component wrapper or null if failed
// component.rerender() - Rerender the component
// component.getElement() - Get the container element
// component.getType() - Get component type

Use case: Dedicated checkout page

Address

const component = await client.injectAddressElement('address-container');

// Returns: IInjectedComponent | null - Component wrapper or null if failed
// component.rerender() - Rerender the component
// component.getElement() - Get the container element
// component.getType() - Get component type

Use case: Shipping address collection page

Product List

await client.injectProductList({
  containerId: 'product-list-container',
  rows: 3,                    // Number of rows per page
  columns: 4,                  // Number of columns
  cardVariant: 'standard',    // Card style variant
  fillCard: false,            // Fill card to available space
  filters: [                  // Optional filters
    'personalization',        // Show personalized products
    'pre-order',              // Show pre-order products
    'delivery-options'         // Show delivery type filters
  ],
  productUrl: '/product/{upc}' // Optional: Product detail page URL template
});

Parameters:

  • containerId - Where to inject the product list
  • rows - Number of rows to display per page (default: 3)
  • columns - Number of columns in the grid (default: 4)
  • cardVariant - Card display style: 'standard' (default)
  • fillCard - Whether cards should fill available space (default: false)
  • filters - Array of filter types to enable: 'personalization', 'pre-order', 'delivery-options'
  • productUrl - Optional URL template for product links. Use {upc} or {grouping} placeholder

Features:

  • ✅ Infinite scroll pagination
  • ✅ Filter by personalization, pre-order, delivery options
  • ✅ Address-aware (reloads when address changes)
  • ✅ Loading states and error handling
  • ✅ Automatic GTM tracking (view_item_list, select_item)
  • ✅ Add to cart directly from cards
  • ✅ Product links with tracking

Use case: Category pages, catalog pages, search results, filtered product browsing

Note: The product list automatically reloads when the address changes to show availability for the new location.

Access All Injected Components

// Get all injected components
const injectedComponents = client.getInjectedComponents();

// Access specific components by container ID
const productComponent = injectedComponents.get('product-container-1');
const cartComponent = injectedComponents.get('cart-container');

// Iterate through all components
injectedComponents.forEach((component, containerId) => {
  console.log(`Container: ${containerId}, Type: ${component.getType()}`);
  
  // Rerender specific components
  if (component.getType() === 'product') {
    component.rerender();
  }
});

// Get all components of a specific type
const productComponents = Array.from(injectedComponents.values())
  .filter(component => component.getType() === 'product');

// Rerender all components of a type
productComponents.forEach(component => component.rerender());

Returns: Map<string, IInjectedComponent> - Map of container IDs to component wrappers

Use cases:

  • Debugging and inspecting injected components
  • Bulk operations on multiple components
  • Component management and cleanup

UI Helpers

Create standalone UI elements that integrate with the SDK.

Cart Button (in container)

client.ui.cartButton('header-cart', true);

Parameters:

  • containerId - Where to place the button
  • showItemsCount - Show item count badge (optional)

Use case: Header navigation, sidebar

Floating Cart Button

client.ui.floatingCartButton(true);

Parameters:

  • showItemsCount - Show item count badge (optional)

Use case: Always-visible cart access (bottom-right corner)

Live Cart Data Display

Bind elements to auto-update with cart data:

// Show live subtotal
client.ui.cartSubtotal('cart-total-display');

// Show live item count (default behavior: hides when count is 0)
client.ui.cartItemsCount('cart-badge');

// Show live item count (always visible, even when 0)
client.ui.cartItemsCount('cart-badge', { hideZero: false });

Parameters for cartItemsCount:

  • elementId (string) - ID of the element to update
  • options (object, optional) - Configuration options:
    • hideZero (boolean, default: true) - When true, element is hidden when cart count is 0. When false, element remains visible showing "0".

Example:

<nav>
  <span>Cart: $<span id="cart-total-display">0.00</span></span>
  <span>(<span id="cart-badge">0</span> items)</span>
</nav>

<script>
  // Default behavior - badge hidden when cart is empty
  client.ui.cartItemsCount('cart-badge');
  
  // Always show count, even when 0
  client.ui.cartItemsCount('cart-badge', { hideZero: false });
</script>

Builder Methods (Development Mode)

When isBuilder: true is set, additional methods are available for theme customization:

const client = await Elements('YOUR_API_KEY', {
  env: 'development',
  isBuilder: true
});

// Update component themes
await client.builder.updateComponentGlobalConfigs(globalTheme);
await client.builder.updateProductComponent(productTheme);
client.builder.updateCartComponent(cartTheme);
client.builder.updateCheckoutComponent(checkoutTheme);
client.builder.updateAddressComponent(addressTheme);

// Builder injection methods (same as regular methods)
const components = await client.builder.injectProductElement(params);
const component = await client.builder.injectCartElement(containerId);
const checkoutComponent = await client.builder.injectCheckoutElement(containerId);
const addressComponent = await client.builder.injectAddressElement(containerId);

// All return IInjectedComponent wrapper objects with rerender(), getElement(), getType() methods

🎬 Actions

Actions provide programmatic control over SDK components. Access them via client.actions or window.elements.actions:

// Available after client initialization
const actions = client.actions;
// OR globally
const actions = window.elements.actions;

Product Actions

// Get product details
const product = actions.product.getDetails('product-123');
console.log(product.name, product.brand, product.region, product.variety);
console.log(product.priceInfo, product.description, product.tastingNotes);

Address Actions

// Set address using Google Places ID
await actions.address.setAddressByPlacesId('ChIJ0SRjyK5ZwokRp1TwT8dJSv8');

// Set address manually without Google Places (perfect for custom address forms)
await actions.address.setAddressManually(
  {
    one: '123 Main St',
    two: 'Apt 4B',          // Optional apartment/suite
    city: 'New York',
    state: 'NY',
    zip: '10001',
    country: 'United States' // Optional, will be included in formatted address
  },
  {
    lat: 40.7505045,
    long: -73.9934387
  }
);

// Listen for success/failure via events
window.addEventListener('lce:actions.address_updated', function(event) {
  const address = event.detail.data;
  console.log('✅ Address set!', address.formattedAddress);
  updateShippingOptions(address.coordinates);
});

window.addEventListener('lce:actions.address_failed', function(event) {
  const error = event.detail.data;
  console.log('❌ Address failed:', error.message);
  showAddressForm();
});

// Get current address
const address = actions.address.getDetails();

// Clear saved address
actions.address.clear();

Clear Address - Complete Reset

The actions.address.clear() action performs a comprehensive reset of the user's address and shopping session:

What it clears:

  • Address Data: Removes all saved address information (street, city, state, zip, coordinates)
  • Cart Contents: Completely resets the cart (removes all items, totals, promo codes)
  • Local Storage: Completely removes the localStorage entry and its value
  • Database: Deletes the persisted store from the server database
  • Checkout State: Resets any pending checkout information

Why it resets the cart: When an address is cleared, the cart must be reset because:

  • Cart items have location-specific pricing and availability
  • Fulfillment options are tied to specific addresses
  • Delivery fees and shipping costs depend on location
  • Without a valid address, cart operations would fail or show incorrect data

Events fired:

  • lce:actions.address_cleared - Address successfully cleared
  • lce:actions.cart_reset - Cart successfully reset

Use cases:

  • Guest checkout option (clear previous user's data)
  • Location change (start fresh with new address)
  • Privacy compliance (complete data removal)
  • Testing/development (reset to clean state)

Notes:

  • To find Google Places IDs for the setAddressByPlacesId action, use the Google Places ID Finder
  • The setAddressManually action automatically generates a Google Places API-formatted address string from the provided components
  • Manual addresses have an empty Places ID (as they don't come from Google Places API)

Action Feedback: All actions provide feedback through events. Listen for success/failure events to handle results and provide user feedback.

Cart Actions

// Control cart visibility
actions.cart.openCart();
actions.cart.closeCart();
actions.cart.toggleCart();

// Add products to cart
await actions.cart.addProduct([{
  identifier: 'product-123',
  fulfillmentType: 'shipping', // or 'onDemand'
  quantity: 2
}]);

// Listen for add product feedback
window.addEventListener('lce:actions.cart_product_add_success', function(event) {
  const { itemsAdded, identifiers } = event.detail.data;
  console.log(`✅ Added ${itemsAdded} products to cart:`, identifiers);
  showSuccessMessage('Products added to cart!');
});

window.addEventListener('lce:actions.cart_product_add_failed', function(event) {
  const { identifiers, error } = event.detail.data;
  console.log(`❌ Failed to add products:`, error);
  showErrorMessage('Could not add products. Please try again.');
});

// Apply promo codes
await actions.cart.applyPromoCode('WELCOME10');

// Listen for promo code feedback
window.addEventListener('lce:actions.cart_promo_code_applied', function(event) {
  const { discountAmount, newTotal } = event.detail.data;
  console.log(`✅ Promo applied! Discount: $${discountAmount}, New total: $${newTotal}`);
  showSavingsMessage(discountAmount);
});

window.addEventListener('lce:actions.cart_promo_code_failed', function(event) {
  const { error } = event.detail.data;
  console.log(`❌ Promo failed:`, error);
  showErrorMessage('Promo code could not be applied');
});

// Remove promo codes
await actions.cart.removePromoCode();

// Get cart details
const cart = actions.cart.getDetails();
console.log(cart.itemCount, cart.amounts.total, cart.amounts.giftCardTotal);

// Reset cart
await actions.cart.resetCart();

Checkout Actions

// Control checkout visibility
actions.checkout.openCheckout();
actions.checkout.closeCheckout();
actions.checkout.toggleCheckout();

// Pre-fill customer information
actions.checkout.updateCustomerInfo({
  firstName: 'John',
  lastName: 'Doe',
  email: '[email protected]',
  phone: '+1234567890'
});

// Pre-fill billing information
actions.checkout.updateBillingInfo({
  firstName: 'John',
  lastName: 'Doe',
  street1: '123 Main St',
  city: 'Anytown',
  state: 'CA',
  zipCode: '12345'
});

// Manage gift options
await actions.checkout.toggleIsGift(true);
actions.checkout.updateGiftInfo({
  giftMessage: 'Happy Birthday!',
  giftFrom: 'Your Friend'
});

// Apply discounts and gift cards
await actions.checkout.applyPromoCode('SAVE20');
await actions.checkout.applyGiftCard('GIFT123');

// Listen for checkout promo code feedback
window.addEventListener('lce:actions.checkout_promo_code_applied', function(event) {
  const { discountAmount, newTotal } = event.detail.data;
  console.log(`✅ Checkout promo applied! Saved: $${discountAmount}`);
  updateCheckoutTotal(newTotal);
});

window.addEventListener('lce:actions.checkout_promo_code_failed', function(event) {
  const { error } = event.detail.data;
  console.log(`❌ Checkout promo failed:`, error);
  showCheckoutError('Promo code could not be applied');
});

// Listen for gift card feedback
window.addEventListener('lce:actions.checkout_gift_card_applied', function(event) {
  const { newTotal } = event.detail.data;
  console.log('✅ Gift card applied successfully!');
  updateCheckoutTotal(newTotal);
  showSuccessMessage('Gift card applied to your order');
});

window.addEventListener('lce:actions.checkout_gift_card_failed', function(event) {
  const { error } = event.detail.data;
  console.log(`❌ Gift card failed:`, error);
  showCheckoutError('Gift card could not be applied');
});

// Get checkout details (safe, non-sensitive data only)
const checkout = actions.checkout.getDetails();
console.log(checkout.itemCount, checkout.amounts.total, checkout.isGift);
console.log(checkout.hasAgeVerify, checkout.hasPromoCode, checkout.hasGiftCards);
console.log(checkout.acceptedAccountCreation, checkout.billingSameAsShipping);
console.log(checkout.marketingPreferences);

// Configure checkout options
await actions.checkout.toggleBillingSameAsShipping(true);
actions.checkout.toggleMarketingPreferences('canEmail', true);

See docs/ACTIONS.md for complete action reference with business use cases.

📡 Events

The SDK emits real-time events for all user interactions. Listen to these events to trigger custom behavior:

// Listen for specific events
window.addEventListener('lce:actions.product_add_to_cart', function(event) {
  const data = event.detail.data;
  console.log('Added to cart:', data.identifier);
  
  // Your custom logic here
  analytics.track('Product Added', {
    identifier: data.identifier,
    quantity: data.quantity,
    upc: data.upc
  });
});

// Or use the helper methods (available after initialization)
window.elements.onAllForms((data, metadata) => {
  console.log('Form Event', { data, metadata });
});

window.elements.onAllActions((data, metadata) => {
  console.log('Action Event', { data, metadata });
});

Available Events

Product Events

  • lce:actions.product_loaded - Product component loaded with comprehensive product details (region, country, abv, proof, age, variety, vintage, descriptions, tasting notes)
  • lce:actions.product_add_to_cart - Item added to cart
  • lce:actions.product_quantity_increase - Quantity increased
  • lce:actions.product_quantity_decrease - Quantity decreased
  • lce:actions.product_size_changed - Product size/variant changed
  • lce:actions.product_fulfillment_type_changed - Delivery method changed

Cart Events

  • lce:actions.cart_loaded - Cart data loaded with complete cart information (itemCount, all amounts including giftCardTotal, detailed item data)
  • lce:actions.cart_opened - Cart displayed
  • lce:actions.cart_closed - Cart hidden
  • lce:actions.cart_updated - Cart contents changed
  • lce:actions.cart_item_added - Item added
  • lce:actions.cart_item_removed - Item removed
  • lce:actions.cart_reset - Cart cleared

Checkout Events

  • lce:actions.checkout_loaded - Checkout data loaded with comprehensive details (acceptedAccountCreation, hasSubstitutionPolicy, billingSameAsShipping, marketing preferences, detailed items)
  • lce:actions.checkout_opened - Checkout started
  • lce:actions.checkout_closed - Checkout abandoned
  • lce:actions.checkout_submit_started - Order submission began
  • lce:actions.checkout_submit_completed - Order completed successfully
  • lce:actions.checkout_submit_failed - Order failed
  • lce:actions.checkout_customer_information_updated - Customer info entered (returns boolean only, no sensitive data)
  • lce:actions.checkout_gift_information_updated - Gift recipient info entered (returns boolean only, no sensitive data)
  • lce:actions.checkout_billing_information_updated - Billing info entered (returns boolean only, no sensitive data)

Security Note: Form update events return only boolean: true to track completion without exposing sensitive customer information (names, emails, phone numbers, addresses, etc.). This protects your customers from malicious scripts and data breaches.

Address Events

  • lce:actions.address_updated - Address information changed
  • lce:actions.address_cleared - Address removed

See docs/EVENTS.md for complete event reference with all available fields and implementation examples.

🎨 Themes & Customization

The SDK provides a theming system that lets you match components to your brand.

Global Theming

const client = await Elements('YOUR_API_KEY', {
  customTheme: {
    global: {
      theme: {
        primaryColor: '#007bff',
        accentColor: '#6c757d',
        successColor: '#28a745',
        errorColor: '#dc3545',
        warningColor: '#ffc107',
        defaultTextColor: '#212529',
        selectedTextColor: '#ffffff',
        linkTextColor: '#1d4ed8',
        drawerBackgroundColor: '#ffffff',
        buttonCornerRadius: '8px',
        cardCornerRadius: '12px',
        headingFont: {
          name: 'Inter',
          weights: [600, 700]
        },
        paragraphFont: {
          name: 'Inter',
          weights: [400, 500]
        }
      },
      layout: {
      enablePersonalization: true,
      personalizationText: 'Customize your product',
      personalizationCardStyle: 'outlined',
      allowPromoCodes: true,
      inputFieldStyle: 'outlined',
      poweredByMode: 'light'
      }
    }
  }
});

Component-Specific Theming

Product Component

customTheme: {
  product: {
    theme: {
      backgroundColor: '#ffffff'
    },
    layout: {
      showImages: true,
      showTitle: true,
      showDescription: true,
      showQuantityCounter: true,
      quantityCounterStyle: 'outlined',
      fulfillmentDisplay: 'carousel',
      enableShippingFulfillment: true,
      enableOnDemandFulfillment: true,
      addToCartButtonText: 'Add to Cart',
      buyNowButtonText: 'Buy Now'
    }
  }
}

Cart Component

customTheme: {
  cart: {
    theme: {
      backgroundColor: '#ffffff'
    },
    layout: {
      showQuantityCounter: true,
      quantityCounterStyle: 'outlined',
      drawerHeaderText: 'Your Cart',
      goToCheckoutButtonText: 'Checkout'
    }
  }
}

Checkout Component

customTheme: {
  checkout: {
    theme: {
      backgroundColor: '#ffffff',
      checkoutCompleted: {
        customLogo: 'https://yourdomain.com/logo.png',
        customText: 'Thank you for your order!'
      }
    },
    layout: {
      emailOptIn: {
        show: true,
        checked: false,
        text: 'Email me with news'
      },
      smsOptIn: {
        show: true,
        checked: false,
        text: 'Text me updates'
      },
      allowGiftCards: true,
      drawerHeaderText: 'Checkout',
      placeOrderButtonText: 'Place Order'
    }
  }
}

Address Component

customTheme: {
  address: {
    theme: {
      backgroundColor: '#ffffff'
    }
  }
}

Dynamic Theme Updates (Builder Mode)

In development with isBuilder: true, update themes in real-time:

const client = await Elements('YOUR_API_KEY', {
  env: 'development',
  isBuilder: true
});

// Update global theme
await client.builder.updateComponentGlobalConfigs({
  theme: { primaryColor: '#ff6b6b' }
});

// Update component-specific themes
await client.builder.updateProductComponent({
  layout: { addToCartButtonText: 'Add to Bag' }
});

client.builder.updateCartComponent({
  layout: { drawerHeaderText: 'Shopping Bag' }
});

client.builder.updateCheckoutComponent({
  layout: { placeOrderButtonText: 'Complete Purchase' }
});

client.builder.updateAddressComponent({
  theme: { backgroundColor: '#f8f9fa' }
});

📖 For complete theming documentation: See docs/THEMING.md


🎁 Features Deep Dive

Product Personalization (Engraving)

The SDK provides a comprehensive personalization/engraving feature for products that support it. The personalization experience varies based on context:

Product View

When browsing products, customers can add personalization through an enhanced form that includes:

  • Product information with pricing
  • Fulfillment/retailer selection (with pricing comparison)
  • Multi-line engraving inputs with character limits
  • Real-time price updates as customers select different retailers
  • Add-to-cart with personalization in one step
// Personalization appears automatically for engravable products
// Customers can add engraving text and select which retailer to fulfill from

// Listen for when personalization is added via add-to-cart
window.addEventListener('lce:actions.product_add_to_cart', (event) => {
  const { hasEngraving, engravingLines } = event.detail.data;
  if (hasEngraving) {
    console.log('Customer personalized:', engravingLines);
  }
});

Cart View

In the cart, personalized items display:

  • Personalization text lines
  • Engraving fee (per item and total for quantity)
  • Edit button to modify the personalization
  • Remove button to remove personalization
// Customers can edit or remove engraving from cart items
window.addEventListener('lce:actions.cart_item_engraving_updated', (event) => {
  const { identifier, engravingLines } = event.detail.data;
  console.log('Cart item engraving updated:', engravingLines);
});

Checkout View

During checkout, personalized items show:

  • Personalization text lines (read-only)
  • Engraving fee included in pricing
  • Remove button only (editing not allowed in checkout)

Design Decision: Editing personalization during checkout is intentionally disabled to prevent order processing complications. Customers must return to the cart to make changes.

Theming & Configuration

Control personalization display through global configuration:

customTheme: {
  global: {
    layout: {
      enablePersonalization: true,
      personalizationText: 'Personalize your product',
      personalizationCardStyle: 'outlined' // or 'filled'
    }
  }
}

Key Features

  • Smart pricing: Automatically includes engraving fees in product price
  • Retailer selection: Compare prices from different retailers during personalization
  • Character limits: Enforces maximum characters per line
  • Uppercase conversion: Engraving text is automatically converted to uppercase
  • Multi-line support: Products can support 1 or more engraving lines
  • Fee transparency: Shows per-item and total fees (e.g., "$5.00 ($2.50 ea)")

Note: Personalization is automatically enabled for products that support it. The SDK handles all UI, validation, and state management.

Gift Options

Allow orders to be marked as gifts with custom messages:

// Enable via theme
customTheme: {
  checkout: {
    layout: {
      allowGiftOptions: true
    }
  }
}

// Toggle gift mode programmatically
await client.actions.checkout.toggleIsGift(true);

// Set gift message
await client.actions.checkout.updateGiftInfo({
  recipientName: 'John Doe',
  message: 'Happy Birthday!'
});

// Listen for gift toggles
window.addEventListener('lce:actions.checkout_is_gift_toggled', (event) => {
  const { isGift } = event.detail.data;
  console.log('Order is gift:', isGift);
});

Tips (On-Demand Delivery)

Allow customers to tip delivery drivers:

// Tips are automatically enabled for onDemand fulfillment types

// Listen for tip updates
window.addEventListener('lce:actions.checkout_tip_updated', (event) => {
  const { tipAmount, total } = event.detail.data;
  console.log(`Customer tipped $${tipAmount}`);
});

Tips are calculated as a percentage or fixed amount and added to the order total.

Gift Cards

Accept gift card payments at checkout:

// Enable via theme
customTheme: {
  checkout: {
    layout: {
      allowGiftCards: true
    }
  }
}

// Apply gift card programmatically
await client.actions.checkout.applyGiftCard('GIFT-1234-5678-9012');

// Remove gift card
await client.actions.checkout.removeGiftCard('GIFT-1234-5678-9012');

// Listen for gift card events
window.addEventListener('lce:actions.checkout_gift_card_applied', (event) => {
  const { newTotal } = event.detail.data;
  console.log(`Gift card applied! New total: $${newTotal}`);
});

window.addEventListener('lce:actions.checkout_gift_card_failed', (event) => {
  const { error } = event.detail.data;
  console.log('Gift card failed:', error);
});

Promo Codes

Apply promotional discount codes:

// Apply to cart
await client.actions.cart.applyPromoCode('SUMMER20');

// Apply to checkout
await client.actions.checkout.applyPromoCode('SAVE10');

// Remove promo code
await client.actions.cart.removePromoCode();

// Listen for promo events
window.addEventListener('lce:actions.cart_promo_code_applied', (event) => {
  const { discountAmount, newTotal } = event.detail.data;
  console.log(`Promo applied! Saved $${discountAmount}! New total: $${newTotal}`);
});

window.addEventListener('lce:actions.cart_promo_code_failed', (event) => {
  const { error } = event.detail.data;
  console.log('Promo failed:', error);
});

Promo Ticker

Display rotating promotional messages at the top of your site:

// Via initialization config
const client = await Elements('YOUR_API_KEY', {
  promoTicker: [
    {
      promoCode: 'FREESHIP',
      text: ['Free Shipping Today!', 'Use code FREESHIP'],
      separator: '•',
      activeFrom: '2025-01-01T00:00:00Z',
      activeUntil: '2025-12-31T23:59:59Z'
    },
    {
      promoCode: 'SAVE20',
      text: ['20% Off Sitewide', 'Limited Time Only'],
      separator: '|',
      activeFrom: '2025-06-01T00:00:00Z',
      activeUntil: '2025-06-30T23:59:59Z'
    }
  ]
});

// Via auto-init
<script
  data-liquid-commerce-elements
  data-token="YOUR_API_KEY"
  data-promo-code="FREESHIP"
  data-promo-text="Free Shipping Today!|Use code FREESHIP"
  data-promo-separator="•"
  data-promo-active-from="2025-01-01T00:00:00Z"
  data-promo-active-until="2025-12-31T23:59:59Z"
  src="https://assets-elements.liquidcommerce.us/all/elements.js"
></script>

The ticker automatically rotates messages and only shows active promotions.

Marketing Preferences

Allow customers to opt-in to email and SMS marketing:

// Set defaults via theme
customTheme: {
  checkout: {
    layout: {
      emailOptIn: { checked: false, visible: true },
      smsOptIn: { checked: false, visible: true }
    }
  }
}

// Update preferences programmatically
await client.actions.checkout.toggleMarketingPreferences('canEmail', true);
await client.actions.checkout.toggleMarketingPreferences('canSms', true);

// Listen for preference changes
window.addEventListener('lce:actions.checkout_marketing_preferences_toggled', (event) => {
  const { field, value } = event.detail.data;
  console.log(`Customer ${value ? 'opted-in' : 'opted-out'} of ${field}`);
});

Purchase Minimum Alerts

Automatically displays alerts when a retailer has minimum purchase requirements. No configuration needed - the SDK handles this automatically based on retailer rules.

Age Verification

For age-restricted products (alcohol, tobacco, etc.), the SDK automatically displays age verification prompts during checkout. This is handled based on product metadata and cannot be disabled for restricted items.

Pre-Sale Countdown

For pre-sale or upcoming products, the SDK automatically displays a countdown timer until the product becomes available. Customers can add pre-sale items to cart, and the SDK handles the special fulfillment flow.

// Listen for when product is added to cart
window.addEventListener('lce:actions.product_add_to_cart', (event) => {
  const { productId } = event.detail.data;
  console.log(`Product ${productId} added to cart`);
});

Product List

Display a filtered, paginated product catalog with infinite scroll. Perfect for category pages, catalog pages, and search results.

Auto-Initialization

Add a product list to any page with data attributes:

<div data-liquid-commerce-elements-products-list
     data-card="standard"
     data-rows="3"
     data-columns="4"
     data-filters="personalization,pre-order,delivery-options"
     data-product-url="/product/{upc}">
</div>

Programmatic Injection

await client.injectProductList({
  containerId: 'product-list-container',
  rows: 3,
  columns: 4,
  cardVariant: 'standard',
  fillCard: false,
  filters: ['personalization', 'pre-order', 'delivery-options'],
  productUrl: '/product/{upc}'
});

Features

Filtering:

  • Personalization filter - Show only products that support engraving/personalization
  • Pre-order filter - Show only pre-order products
  • Delivery options filter - Filter by all, shipping, or onDemand

Smart Filter Logic:

  • When "Same-Day Delivery" is selected, personalization and pre-order filters are automatically disabled
  • When personalization or pre-order is enabled, same-day delivery is automatically disabled
  • Filters work together intelligently to show only compatible product combinations

Infinite Scroll:

  • Automatically loads more products as you scroll
  • Shows loading states during pagination
  • Handles "no more products" gracefully

Address-Aware:

  • Automatically reloads when address changes
  • Shows availability based on current location
  • Updates product pricing and fulfillment options dynamically

Product Cards:

  • Display product image, name, size, and price
  • Show availability status based on location
  • "Add to Cart" button (disabled if unavailable)
  • Clickable product links (if productUrl is provided)
  • Presale product support with special handling

Analytics Tracking:

  • Automatically tracks view_item_list when products load
  • Tracks select_item when user clicks a product link
  • GTM integration for e-commerce events

Loading States:

  • Shows skeleton loading cards during initial load
  • Displays loading indicator during infinite scroll
  • Error handling with user-friendly messages

Product URL Templates:

  • Use {upc} placeholder for UPC-based URLs: /product/{upc}
  • Use {grouping} placeholder for Salsify grouping IDs: /product/{grouping}
  • Links open in new tab with proper security attributes

Example: Category Page

<div id="category-products"
     data-liquid-commerce-elements-products-list
     data-card="standard"
     data-rows="4"
     data-columns="4"
     data-filters="personalization,delivery-options"
     data-product-url="/products/{upc}">
</div>

<script
  data-liquid-commerce-elements
  data-token="YOUR_API_KEY"
  data-env="production"
  src="https://assets-elements.liquidcommerce.us/all/elements.js"
></script>

Example: Search Results

const client = await Elements('YOUR_API_KEY', {
  env: 'production'
});

await client.injectProductList({
  containerId: 'search-results',
  rows: 6,
  columns: 3,
  cardVariant: 'standard',
  fillCard: true,
  filters: ['delivery-options'],
  productUrl: '/search?q={grouping}'
});

Use cases:

  • Category pages
  • Catalog pages
  • Search results
  • Filtered product browsing
  • Personalized product recommendations
  • Pre-order product showcases

🔧 Core Capabilities

The SDK includes several built-in services that work behind the scenes to provide a robust, production-ready experience.

State Management

The SDK uses a centralized store for all state management. Access state data via actions:

// Get current cart state
const cart = await client.actions.cart.getDetails();
console.log(cart.itemCount, cart.amounts.total, cart.amounts.giftCardTotal);

// Get current checkout state
const checkout = await client.actions.checkout.getDetails();
console.log(checkout.amounts.total, checkout.isGift, checkout.acceptedAccountCreation);
console.log(checkout.billingSameAsShipping, checkout.marketingPreferences);

// Get current address
const address = await client.actions.address.getDetails();
console.log(address.formattedAddress, address.coordinates);

// Get product details
const product = await client.actions.product.getDetails('00619947000020');
console.log(product.name, product.region, product.variety, product.vintage);
console.log(product.description, product.tastingNotes);

State is persistent: Cart and address data persist across page reloads using localStorage.

Event System (PubSub)

All SDK interactions emit events through a centralized event system:

// Subscribe to specific event
window.addEventListener('lce:actions.cart_updated', (event) => {
  const { previous, current } = event.detail.data;
  console.log('Cart changed from', previous.amounts.total, 'to', current.amounts.total);
});

// Subscribe to all action events
if (window.elements) {
  window.elements.onAllActions((data, metadata) => {
    console.log('Action:', metadata.eventName, data);
  });
}

// Subscribe to all form events
if (window.elements) {
  window.elements.onAllForms((data, metadata) => {
    console.log('Form:', metadata.eventName, data);
  });
}

Event format:

{
  detail: {
    data: { /* event-specific payload */ },
    metadata: {
      eventName: 'lce:actions.cart_updated',
      timestamp: 1699564800000,
      source: 'sdk'
    }
  }
}

Telemetry & Analytics

The SDK automatically tracks user interactions and performance metrics:

  • User interactions: Add to cart, checkout started, checkout completed
  • Performance metrics: Component load times, API response times
  • Error tracking: Failed API calls, validation errors

Note: The SDK includes automatic Google Tag Manager (GTM) integration that tracks e-commerce events. GTM configuration is managed through your LiquidCommerce dashboard, not the client initialization.

Custom Analytics:

// Listen to events for custom analytics tracking
window.addEventListener('lce:actions.product_add_to_cart', (event) => {
  const { productId, price, quantity } = event.detail.data;
  
  // Track with your analytics provider
  gtag('event', 'add_to_cart', {
    currency: 'USD',
    value: price * quantity,
    items: [{ item_id: productId, quantity }]
  });
  
  // Or Segment
  analytics.track('Product Added', {
    product_id: productId,
    price,
    quantity
  });
});

Fingerprinting

The SDK generates a unique device fingerprint for fraud prevention and analytics:

// Automatically tracked:
// - Browser fingerprint
// - Device characteristics
// - Session information

// Used for:
// - Fraud detection
// - Cart persistence across devices
// - Personalization

Fingerprinting is handled automatically and requires no configuration.

Authentication

The SDK handles authentication automatically using your API key:

const client = await Elements('YOUR_API_KEY', {
  env: 'production'
});

// All API requests include:
// - API key authentication
// - CORS headers
// - Request signing (when required)

Token refresh: The SDK automatically handles token expiration and refresh.

Logger

Built-in logging system with configurable levels:

const client = await Elements('YOUR_API_KEY', {
  debugMode: 'console'  // 'none' | 'console' | 'panel'
});

// Debug mode options:
// - 'none': No logging (production default)
// - 'console': Logs to browser console
// - 'panel': Shows visual debug panel + console logs

Console debug output:

[LCE SDK] Product loaded: product-123
[LCE SDK] Cart updated: 3 items, $45.99
[LCE SDK] Checkout started
[LCE SDK] API call: POST /cart/add (152ms)

Debug panel:

  • Real-time event stream
  • API call inspector
  • State viewer
  • Performance metrics

Command Pattern

The SDK uses a command pattern for all operations:

// Commands are:
// - Queued for execution
// - Retried on failure
// - Logged for debugging
// - Cancelable

// Example: Adding to cart
// 1. Command created: AddToCartCommand
// 2. Command queued
// 3. Command executed
// 4. Success event emitted
// 5. UI updated

// This ensures:
// - Consistent error handling
// - Automatic retries
// - Full audit trail

Component Factory

Components are created on-demand using a factory pattern:

// When you call:
const components = await client.injectProductElement([
  { containerId: 'pdp-1', identifier: 'product-123' }
]);
// Returns: IInjectedComponent[] - Array of component wrappers

// The SDK:
// 1. Creates a ProductComponent instance
// 2. Registers it with the factory
// 3. Injects it into the DOM
// 4. Attaches event listeners
// 5. Loads product data
// 6. Renders the component

// Components are:
// - Lazily loaded
// - Automatically cleaned up
// - Reusable across pages

Singleton Manager

Core services are managed as singletons:

// These services are initialized once and reused:
// - ApiClient
// - Store
// - PubSub
// - Logger
// - Telemetry
// - Fingerprint
// - Auth

// This ensures:
// - Consistent state
// - Efficient memory usage
// - No duplicate API calls

🏗️ Integration Patterns

React Integration

import { useEffect, useState } from 'react';
import { Elements } from '@liquidcommerce/elements-sdk';

function ProductPage({ productId }) {
  const [client, setClient] = useState(null);

  useEffect(() => {
    async function initSDK() {
      const elementsClient = await Elements(process.env.REACT_APP_LCE_API_KEY, {
        env: 'production'
      });
      setClient(elementsClient);

      // Inject product
      await elementsClient.injectProductElement([
        { containerId: 'product-container', identifier: productId }
      ]);

      // Create cart button
      elementsClient.ui.cartButton('cart-button', true);
    }

    initSDK();

    // Cleanup
    return () => {
      // SDK handles cleanup automatically
    };
  }, [productId]);

  return (
    <div>
      <div id="cart-button"></div>
      <div id="product-container"></div>
    </div>
  );
}

Vue Integration

<template>
  <div>
    <div ref="cartButton"></div>
    <div ref="productContainer"></div>
  </div>
</template>

<script>
import { Elements } from '@liquidcommerce/elements-sdk';

export default {
  name: 'ProductPage',
  props: ['productId'],
  async mounted() {
    this.client = await Elements(process.env.VUE_APP_LCE_API_KEY, {
      env: 'production'
    });

    await this.client.injectProductElement([
      { containerId: this.$refs.productContainer.id, identifier: this.productId }
    ]);

    this.client.ui.cartButton(this.$refs.cartButton.id, true);
  }
}
</script>

Next.js Integration

'use client';

import { useEffect } from 'react';

export default function ProductPage({ productId }: { productId: string }) {
  useEffect(() => {
    // Load SDK script
    const script = document.createElement('script');
    script.src = 'https://assets-elements.liquidcommerce.us/all/elements.js';
    script.async = true;
    script.onload = async () => {
      const client = await (window as any).Elements(process.env.NEXT_PUBLIC_LCE_API_KEY, {
        env: 'production'
      });

      const components = await client.injectProductElement([
        { containerId: 'product-container', identifier: productId }
      ]);

      client.ui.floatingCartButton(true);
    };
    document.body.appendChild(script);

    return () => {
      document.body.removeChild(script);
    };
  }, [productId]);

  return <div id="product-container"></div>;
}

WordPress Integration

<!-- In your theme's header.php or functions.php -->
<script
  data-liquid-commerce-elements
  data-token="<?php echo get_option('lce_api_key'); ?>"
  data-env="production"
  src="https://assets-elements.liquidcommerce.us/all/elements.js"
></script>

<!-- In your product template -->
<div data-lce-product="<?php echo get_post_meta(get_the_ID(), 'product_upc', true); ?>"></div>

Shopify Integration

<!-- In theme.liquid -->
<script
  data-liquid-commerce-elements
  data-token="{{ settings.lce_api_key }}"
  data-env="production"
  src="https://assets-elements.liquidcommerce.us/all/elements.js"
></script>

<!-- In product template -->
<div data-lce-product="{{ product.barcode }}"></div>

Multi-Page Applications

For SPAs and multi-page apps, initialize once and reuse:

// app.js (initialize once)
let elementsClient = null;

async function getElementsClient() {
  if (!elementsClient) {
    elementsClient = await Elements('YOUR_API_KEY', {
      env: 'production'
    });
  }
  return elementsClient;
}

// product-page.js
const client = await getElementsClient();
await client.injectProductElement([
  { containerId: 'pdp-1', identifier: productId }
]);

// cart-page.js
const client = await getElementsClient();
const cartComponent = await client.injectCartElement('cart-container');

🚨 Error Handling

Initialization Errors

try {
  const client = await Elements('YOUR_API_KEY', {
    env: 'production'
  });
} catch (error) {
  if (error.message.includes('Invalid API key')) {
    console.error('Authentication failed');
  } else if (error.message.includes('Network')) {
    console.error('Network error - check connectivity');
  } else {
    console.error('SDK initialization failed:', error);
  }
}

Action Errors

All actions emit failure events with detailed error information:

// Product loaded successfully
window.addEventListener('lce:actions.product_loaded', (event) => {
  const { identifier, productData } = event.detail.data;
  console.log(`Product ${identifier} loaded successfully`);
});

// Cart action failure
window.addEventListener('lce:actions.cart_product_add_failed', (event) => {
  const { identifiers, error } = event.detail.data;
  console.error('Failed to add products:', error);
  
  // Show user-friendly message
  showNotification('Could not add to cart. Please try again.', 'error');
});

// Checkout failure
window.addEventListener('lce:actions.checkout_submit_failed', (event) => {
  const { error, reason } = event.detail.data;
  console.error('Checkout failed:', reason);
  
  // Handle specific errors
  if (reason.includes('payment')) {
    showNotification('Payment declined. Please check your card details.', 'error');
  } else if (reason.includes('inventory')) {
    showNotification('Some items are no longer available.', 'warning');
  } else {
    showNotification('Checkout failed. Please try again.', 'error');
  }
});

// Address validation failure
window.addEventListener('lce:actions.address_failed', (event) => {
  const { error } = event.detail.data;
  console.error('Address validation failed:', error);
  showNotification('Please enter a valid address.', 'error');
});

// Promo code failure
window.addEventListener('lce:actions.cart_promo_code_failed', (event) => {
  const { code, error } = event.detail.data;
  console.error(`Promo code ${code} failed:`, error);
  
  if (error.includes('expired')) {
    showNotification('This promo code has expired.', 'warning');
  } else if (error.includes('invalid')) {
    showNotification('Invalid promo code.', 'error');
  } else if (error.includes('minimum')) {
    showNotification('Cart minimum not met for this promo.', 'warning');
  }
});

Network Error Recovery

let retryCount = 0;
const maxRetries = 3;

async function addProductWithRetry(productParams) {
  try {
    await client.actions.cart.addProduct(productParams);
  } catch (error) {
    if (retryCount < maxRetries && error.message.includes('Network')) {
      retryCount++;
      console.log(`Retrying... Attempt ${retryCount}/${maxRetries}`);
      setTimeout(() => addProductWithRetry(productParams), 1000 * retryCount);
    } else {
      console.error('Failed after retries:', error);
      showNotification('Network error. Please check your connection.', 'error');
    }
  }
}

Global Error Handler

// Listen to all failed events
window.addEventListener('lce:actions.cart_failed', handleError);
window.addEventListener('lce:actions.checkout_failed', handleError);
window.addEventListener('lce:actions.address_failed', handleError);
window.addEventListener('lce:actions.cart_product_add_failed', handleError);

function handleError(event) {
  const { error, context } = event.detail.data;
  
  // Log to error tracking service
  if (window.Sentry) {
    Sentry.captureException(error, {
      tags: { sdk: 'liquid-commerce' },
      extra: context
    });
  }
  
  // Show user notification
  showNotification('Something went wrong. Please try again.', 'error');
}

⚡ Performance & Best Practices

Lazy Loading Components

Only inject components when needed:

// ❌ Don't inject all components upfront
await client.injectProductElement([/* 50 products */]);
await client.injectCartElement('cart');
await client.injectCheckoutElement('checkout');

// ✅ Inject components as user navigates
// On product page:
await client.injectProductElement([{ containerId: 'pdp', identifier: productId }]);

// On cart page (when user clicks cart):
await client.injectCartElement('cart');

// On checkout (when user proceeds):
await client.injectCheckoutElement('checkout');

Reuse Client Instance

Initialize once, use everywhere:

// ❌ Don't create multiple clients
// page1.js
const client1 = await Elements('KEY', { env: 'production' });
// page2.js
const client2 = await Elements('KEY', { env: 'production' });

// ✅ Create once, reuse
// app.js
window.lceClient = await Elements('KEY', { env: 'production' });

// page1.js
const client = window.lceClient;
await client.injectProductElement([...]);

// page2.js
const client = window.lceClient;
await client.injectCartElement('cart');

Batch Product Injections

Group product injections together:

// ❌ Don't inject products one by one
await client.injectProductElement([{ containerId: 'p1', identifier: 'id1' }]);
await client.injectProductElement([{ containerId: 'p2', identifier: 'id2' }]);
await client.injectProductElement([{ containerId: 'p3', identifier: 'id3' }]);

// ✅ Inject all products at once
await client.injectProductElement([
  { containerId: 'p1', ide