Message Events

The Stylux SDK publishes lifecycle messages through Stylux.pubsub. Use these topics to observe SDK behavior or coordinate user interface updates.

For example:

Stylux.pubsub.subscribe('CLOSE_MODAL', (payload) => {
  // respond to the modal closing
});

Message Topics

TopicPayload
SETUP_COMPLETEundefined
SHOW_OFFER{ type }
HIDE_OFFER{ type }
OPEN_MODALModal state
CLOSE_MODALModal state and closingSource
BUNDLE_CREATEBundle, product, offer, entries, and attribution
URL_CHANGECurrent and previous URL

The primary modal and bundle payloads have the following shape:

interface IModalState {
  openingSource: IModalOpeningSource;
  offer: IOffer;
  product: IProduct;
}

interface IModalClosingState {
  openingSource: IModalOpeningSource;
  closingSource:
    'NO_THANKS' | 'CLOSE_BUTTON' | 'BACKDROP' | 'ESCAPE' | 'UNKNOWN';
  offer: IOffer;
  product: IProduct;
}

interface IBundleCreateData {
  attribution: IModalOpeningSource;
  baseProduct: IProductBundleCreate;
  bundle: IBundleApiResponse;
  entries: PersonalizationEntryInput[];
  metadata?: IB2BBundleMetadata | null;
  offer: IOffer;
  offerOptionPlacementInfo: IOfferOptionPlacementInfo;
  product: IProductBundleCreate;
}

Bundle Creation

Prefer a cart adapter when you use the Stylux modal. The SDK then resolves the base or replacement product, required upsell products, quantities, and line item properties, and writes the cart before publishing BUNDLE_CREATE. Do not add products to cart again from this topic when an adapter is registered.

If adapter checkout or transaction completion throws, the bundle may already exist in Stylux, but BUNDLE_CREATE is not published.

The following cart write pattern is for integrations that do not register a cart adapter: using the Stylux modal without an adapter, or a fully custom experience that does not use the Stylux modal. You must resolve associated products from the offer and submitted entries, set Stylux line properties, and keep related lines balanced. See Cart Balancing and Normalization.

product on the payload is already the replacement product when the offer has an associated product of type REPLACEMENT. product.productId is the external platform ID (on Shopify, the variant ID).

Typed upsells are added when the matching entry includes a placement:

  • simpleText.placementIdTEXT_UPSELL
  • icon.placementIdICON_UPSELL
  • monogram.placementIdMONOGRAM_UPSELL
  • imageUpload.placementIdIMAGE_UPLOAD_UPSELL

When offer.fulfillmentType is UPSELL, also add the associated product of type UPSELL. For that fulfillment type the base line does not receive _STYLUX_BUNDLE.

TEXT_FONT, TEXT_COLOR, TEXT_PLACEMENT, SIMPLE_MULTIPLE_CHOICE, and MONOGRAM_COLOR entries do not add their own associated products.

Stylux.pubsub.subscribe(
  'BUNDLE_CREATE',
  ({ product, offer, bundle, entries }) => {
    if (!bundle) {
      return;
    }

    const associatedProductsByType = new Map();

    offer?.associatedProducts?.nodes.forEach((associatedProduct) => {
      if (associatedProduct.product) {
        associatedProductsByType.set(
          associatedProduct.type,
          associatedProduct.product,
        );
      }
    });

    const upsellProductId = associatedProductsByType.get('UPSELL')?.productId;

    const textUpsellProductId =
      associatedProductsByType.get('TEXT_UPSELL')?.productId;

    const iconUpsellProductId =
      associatedProductsByType.get('ICON_UPSELL')?.productId;

    const monogramUpsellProductId =
      associatedProductsByType.get('MONOGRAM_UPSELL')?.productId;

    const imageUploadUpsellProductId = associatedProductsByType.get(
      'IMAGE_UPLOAD_UPSELL',
    )?.productId;

    const hasTextEntriesWithPlacement = entries.some(
      (entry) => entry.simpleText?.placementId,
    );

    const hasIconEntriesWithPlacement = entries.some(
      (entry) => entry.icon?.placementId,
    );

    const hasMonogramEntriesWithPlacement = entries.some(
      (entry) => entry.monogram?.placementId,
    );

    const hasImageUploadEntriesWithPlacement = entries.some(
      (entry) => entry.imageUpload?.placementId,
    );

    const isUpsellFulfillment = offer?.fulfillmentType === 'UPSELL';

    const products = [
      {
        productId: product.productId,
        properties: {
          ...(isUpsellFulfillment ? {} : { _STYLUX_BUNDLE: bundle.id }),
          _STYLUX_PRODUCT_TYPE: 'BASE',
        },
      },
    ];

    const addUpsell = ({ productId, upsellType }) => {
      products.push({
        productId,
        properties: {
          _STYLUX_BUNDLE: bundle.id,
          _STYLUX_PRODUCT_TYPE: upsellType,
        },
      });
    };

    if (isUpsellFulfillment && upsellProductId) {
      addUpsell({ productId: upsellProductId, upsellType: 'UPSELL' });
    }

    if (hasTextEntriesWithPlacement && textUpsellProductId) {
      addUpsell({ productId: textUpsellProductId, upsellType: 'TEXT_UPSELL' });
    }

    if (hasIconEntriesWithPlacement && iconUpsellProductId) {
      addUpsell({ productId: iconUpsellProductId, upsellType: 'ICON_UPSELL' });
    }

    if (hasMonogramEntriesWithPlacement && monogramUpsellProductId) {
      addUpsell({
        productId: monogramUpsellProductId,
        upsellType: 'MONOGRAM_UPSELL',
      });
    }

    if (hasImageUploadEntriesWithPlacement && imageUploadUpsellProductId) {
      addUpsell({
        productId: imageUploadUpsellProductId,
        upsellType: 'IMAGE_UPLOAD_UPSELL',
      });
    }

    return addToCart({ products });
  },
);

Replace addToCart with your storefront cart API. Associated products can include priceTiers; the SDK checkout path selects the tier product for the current quantity.


Did this page help you?