Cart Adapter

A cart adapter connects the Stylux JavaScript SDK to your storefront cart. The SDK uses it to add personalized products, keep bundle quantities balanced, and read cart lines for cart offers.

The Shopify plugin registers an adapter for standard Shopify themes. Headless and custom storefronts should provide their own. Register it in the setup call after you load the SDK:

await Stylux.setup({
  apiKey: 'STLX_key-for-unsigned-requests',
  merchantId: 'merchant-id',
  cart: {
    adapter: cartAdapter,
    mutationPathFragments: ['/api/cart'],
    normalize: true,
  },
});

When an adapter is registered, the SDK completes the cart transaction, including side effects for that action, before publishing BUNDLE_CREATE. Without an adapter, the SDK creates the bundle and publishes the topic, but does not mutate the cart.

Interface

An adapter implements ICartAdapter:

interface ICartAdapter {
  getCart(): Promise<StyluxCart>;
  getQuantity?(): number;
  addItemsToCart(args: AddItemsToCartArgs): Promise<StyluxCart>;
  updateCart(args: UpdateCartArgs): Promise<StyluxCart>;
  clear(): Promise<StyluxCart>;
  transact<T>(args: CartTransactArgs<T>): Promise<T>;
}

getCart returns the current platform cart mapped to the StyluxCart shape.

getQuantity is optional. Offer flows that do not specify a quantity use it as the quantity added to cart. If you omit it, the SDK uses 1. If the PDP has a quantity selector, getQuantity should return the currently selected quantity.

addItemsToCart, updateCart, and clear are silent write primitives. They change cart state and return the updated snapshot. They should not redirect, reload the page, refresh cart sections, or call merchant hooks after the write. Those side effects belong in transact.

Cart Shape

type StyluxCart = {
  items: StyluxCartLine[];
  attributes: Record<string, string>;
};

type StyluxCartLine = {
  lineItemId: string;
  price: number;
  productId: string;
  quantity: number;
  parentLineItemId?: string | null;
  properties?: Record<string, string>;
};

type StyluxCartLineInput = {
  productId: string;
  quantity: number;
  parentLineItemId?: string | null;
  properties?: Record<string, string>;
};

lineItemId must identify one cart line and remain stable while that line exists. productId is the external platform ID as a string, not the internal Stylux product UUID. On Shopify this is the variant ID. price uses the storefront cart's numeric price representation.

properties contains line item metadata. Stylux checkout supplies properties such as _STYLUX_BUNDLE when it calls addItemsToCart. Preserve all provided properties when translating the input to your cart platform.

parentLineItemId carries the parent relationship for a nested child line. Its write input is based on the base product being added; the returned snapshot should use the parent identifier from the cart platform. Platforms without nested cart lines can ignore it.

Item Order

The order of getCart().items must match the order of line items in the cart UI the customer sees, including a slide-out cart and the cart page.

  • The first visible cart line corresponds to items[0]
  • The second visible cart line corresponds to items[1]
  • The remaining lines continue in the same order

Cart item upsells follow that same visible order, so they appear on the line the customer is looking at. Sorting by product ID, reversing lines, or omitting lines shown in the UI can put an upsell on the wrong item. Those offers also require a registered adapter, and each line's productId must be the same external platform ID Stylux stores (on Shopify, the variant ID).

Adding Items

addItemsToCart receives one of two actions:

type AddItemsToCartArgs =
  | {
      action: 'ADD_TO_CART';
      context: AddToCartContext;
      items: StyluxCartLineInput[];
    }
  | {
      action: 'CART_NORMALIZATION';
      items: StyluxCartLineInput[];
    };

ADD_TO_CART represents an offer or checkout flow. Its context includes attribution and product details that an adapter may use for behavior specific to checkout.

CART_NORMALIZATION is a mechanical add used to restore missing bundle lines. It should skip behavior specific to checkout.

Updating and Clearing

type UpdateCartArgs = {
  attributes?: Record<string, string> | null;
  lineItemQuantitiesByLineItemId?: Record<string, number> | null;
};

Omit fields that are not changing. Quantity updates are keyed by lineItemId; a quantity of 0 removes the line. Attributes merge onto the existing cart attributes. clear removes all lines and returns the resulting snapshot.

Transactions and Side Effects

transact is the public API for side effects associated with a cart action. It is not another cart write method. Cart writes happen inside run through addItemsToCart, updateCart, or clear.

type CartTransactArgs<T> = {
  action:
    | 'ADD_TO_CART'
    | 'CART_NORMALIZATION'
    | 'UPDATE_CART'
    | 'CLEAR_CART'
    | 'UPDATE_ATTRIBUTES';
  run: () => Promise<T>;
};

An adapter should:

  1. Call run
  2. Return or rethrow if run fails; do not run completion side effects
  3. Run completion side effects for action after a successful run
  4. Return the result from run

The action describes why the transaction ran:

ActionMeaning
ADD_TO_CARTAn offer or checkout add finished
CART_NORMALIZATIONStylux rewrote quantities or lines for consistency
UPDATE_CARTAn explicit line quantity update finished
CLEAR_CARTThe cart was cleared
UPDATE_ATTRIBUTESCart attributes changed without a line item UI update

When transact is called, check action and run the side effects that action needs, such as a redirect, reload, cart UI refresh, or merchant hook. Keep those behaviors out of the silent write methods.

If the adapter has no completion behavior, transact can be ({ run }) => run(). Otherwise implement it like this:

async transact({ action, run }) {
  const result = await run();
  await refreshCartUi({ action });
  return result;
}

Transform and Cache

An adapter is mostly translation. Every method takes Stylux input, calls your platform cart API, and transforms the platform response back into the StyluxCart shape. Line inputs travel the other direction: map productId, quantity, parentLineItemId, and all properties onto whatever your platform expects, without dropping fields.

Keep a transformed cart in memory. The SDK can call getCart several times during one checkout, normalization pass, or cart item upsell render, so refetching every time is usually more work than you want. Return the cached cart when nothing has changed. After addItemsToCart, updateCart, or clear, cache the transformed result so the next read is already current. If a write does not give you a cart back, fetch one and cache that instead. When the storefront changes the cart on its own, such as quantity controls on the cart page, drop the cache so the next getCart sees the real cart. The SDK does not care how the cache is structured, only that getCart stays cheap and returns a current StyluxCart.

function createCartAdapter({ cartApi }) {
  return {
    async getCart() {
      // return a cached cart when you already have one
      return toStyluxCart(await cartApi.getCart());
    },

    async addItemsToCart({ items }) {
      const cart = await cartApi.addItems(items.map(toPlatformLine));
      // cache the transformed cart
      return toStyluxCart(cart);
    },

    async updateCart(args) {
      const cart = await cartApi.updateCart(args);
      // cache the transformed cart
      return toStyluxCart(cart);
    },

    async clear() {
      const cart = await cartApi.clear();
      // cache the transformed cart
      return toStyluxCart(cart);
    },

    async transact({ action, run }) {
      const result = await run();
      await refreshCartUi({ action });
      return result;
    },
  };
}

toStyluxCart maps your platform cart into the StyluxCart shape: lines in the same order as the cart UI, productId as the external platform ID (on Shopify, the variant ID), stable lineItemId values, and every property the platform returned. toPlatformLine does the reverse for writes, mapping each StyluxCartLineInput onto your platform's add payload, including productId, quantity, parentLineItemId when you use nested lines, and all properties.

Normalization Triggers

Set cart.normalize to let the SDK balance fulfillment bundles with more than one line created through SDK checkout. Set cart.mutationPathFragments to match the storefront requests that change the cart. After a matching request finishes, the SDK runs normalization again.

String entries still match URL path fragments with pathname.includes(...). Entries may also be:

  • a regular expression against the pathname
  • a function that receives { body, method, url }
  • an object with any combination of path, method, and body

Every field in an object matcher must match. path accepts a string fragment, a regular expression, or a pathname function. method is case insensitive and may be a string or an array of methods. body accepts a regular expression or a function that receives the request body string, or null when no body is available.

cart: {
  adapter: cartAdapter,
  normalize: true,
  mutationPathFragments: [
    '/api/cart',
    {
      path: '/graphql',
      method: 'POST',
      body: /AddCartLines/,
    },
  ],
}

The SDK checks path and method first. It only reads the request body when a matcher needs it, and only after those cheaper checks pass. Body matching requires the storefront to use fetch; XMLHttpRequest bodies are not available.

The initial normalization pass also requires at least one effective matcher. The Shopify plugin supplies defaults for Shopify AJAX cart endpoints (cart/change, cart/update, cart/add) when the option is omitted. Explicit matchers replace those defaults; an empty array disables normalization.

Custom Adapter with the Shopify Plugin

A cart.adapter you provide is registered before plugins load, and the Shopify plugin does not replace it. Shopify checkout behavior lives in its built-in adapter, including merchant hooks, selling plans, extra line item properties, the default /cart redirect, and cart page reloads.

If you enable the plugin with a custom adapter, implement the behavior you need in that adapter or wrap the built-in Shopify adapter. Shopify user experience helpers, such as variant change detection and add to cart override, remain available.


Did this page help you?