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:
- Call
run - Return or rethrow if
runfails; do not run completion side effects - Run completion side effects for
actionafter a successfulrun - Return the result from
run
The action describes why the transaction ran:
| Action | Meaning |
|---|---|
ADD_TO_CART | An offer or checkout add finished |
CART_NORMALIZATION | Stylux rewrote quantities or lines for consistency |
UPDATE_CART | An explicit line quantity update finished |
CLEAR_CART | The cart was cleared |
UPDATE_ATTRIBUTES | Cart 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, andbody
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.
Updated about 1 hour ago
