Last updated

Cart Operation Events

Every public cart mutation emits a uniform success or error window event, so a storefront can react to any cart change from one place. The CDN bundle can also render a built-in toast for these events and show a loading spinner on add-to-cart buttons β€” both are opt-in (off by default), so existing storefronts see no change until they turn them on.

Before this, a failed addCartItems / addEnrollmentPack could fail silently β€” the shopper saw nothing. Now the failure always surfaces as an event, and β€” once enabled β€” as a toast.

Opt-in switches (on the Fluid script tag): data-fluid-toast="true" enables the toast, data-fluid-button-loading="true" enables the button spinner. The events fire regardless.


The Events

These functions emit the events:

FunctionCategoryOn successOn failure
addCartItemsaddCART_OPERATION_SUCCESSCART_OPERATION_ERROR (still resolves undefined)
addEnrollmentPackaddCART_OPERATION_SUCCESSCART_OPERATION_ERROR (rethrows)
updateCartItemsupdateCART_OPERATION_SUCCESSCART_OPERATION_ERROR (rethrows)
decrementCartItemupdateCART_OPERATION_SUCCESSCART_OPERATION_ERROR (still resolves undefined)
updateCartItemVariantupdateCART_OPERATION_SUCCESSCART_OPERATION_ERROR (rethrows)
removeCartItemByIdremoveCART_OPERATION_SUCCESSCART_OPERATION_ERROR (rethrows)

These events are additive β€” the SDK's existing lower-level events (ADD_TO_CART, UPDATE_CART_ITEM, REMOVE_FROM_CART, CART_LOADING_START / CART_LOADING_END) still fire. Cart operation events are the single, uniform signal that covers all six mutations.

Listening

window.addEventListener("CART_OPERATION_SUCCESS", (e) => {
  console.log(e.detail.operation, "succeeded", e.detail.cart);
});

window.addEventListener("CART_OPERATION_ERROR", (e) => {
  console.log(e.detail.operation, "failed:", e.detail.message);
});

Event Detail

Both events carry a detail object with the same shape:

FieldTypeWhenDescription
operationstringalwaysThe function that ran, e.g. "addCartItems"
category"add" | "update" | "remove"alwaysCoarse bucket used to pick the default toast message
toastbooleanalwaysfalse when the call passed { toast: false } β€” the built-in toast skips it
timestampstringalwaysISO timestamp
cartCartsuccessThe resulting cart, when the operation returned one
errorunknownerrorThe raw thrown value
messagestringerrorBest-effort human-readable message
variantIdsnumber[]when knownVariant IDs the operation acted on
itemIdnumberwhen knownCart item ID (for item-scoped operations)
enrollmentPackIdnumberwhen knownEnrollment pack ID (for addEnrollmentPack)
// Example error detail
{
  operation: "addCartItems",
  category: "add",
  toast: true,
  timestamp: "2026-07-13T20:00:00.000Z",
  error: Error,
  message: "Something went wrong",
  variantIds: [11111]
}

Built-in Toast

The CDN bundle can render a toast for these events. It is opt-in (off by default) β€” add data-fluid-toast="true" to the Fluid script tag to turn it on. Nothing else is required; it renders in the light DOM, so your CSS applies to it directly.

Once enabled, the toast has a variant icon, the message, and a close button; it slides in, auto-dismisses after 4s, pauses on hover, and honors prefers-reduced-motion. Success toasts are suppressed while the cart drawer is open (an open cart is its own confirmation); error toasts always show.

<script
  id="fluid-cdn-script"
  src="https://assets.fluid.app/scripts/fluid-sdk/latest/web-widgets/index.js"
  data-fluid-shop="your-shop-id"
  data-fluid-toast="true"
></script>

Configure at runtime (configureCartFeedback)

When you can't edit the Fluid script tag β€” e.g. it's injected as a global embed, which is the usual case for themes β€” enable and configure the toast (and the button spinner) at runtime instead:

window.addEventListener("DOMContentLoaded", () => {
  window.FairShareSDK?.configureCartFeedback({
    toast: true,
    buttonLoading: true,
    position: "bottom-right",
    class: "my-toast",
    duration: 5000,
    messages: { add: "Added! πŸŽ‰", error: "Couldn't add that β€” try again." },
  });
});

Accepts { toast, buttonLoading, position, class, duration, icon, successIcon, errorIcon, closeIcon, messages } β€” all optional. It merges over the script-tag attributes below (precedence: configureCartFeedback() > attributes > defaults) and is read lazily, so it applies whenever you call it. Call it once the SDK is attached (DOMContentLoaded is reliable for the deferred embed); the optional chaining makes it a safe no-op otherwise. Theme authors: see Cart Feedback.

Configuration (script-tag attributes)

If you do control the script tag, configure it with data-fluid-toast-* attributes on the Fluid script tag (#fluid-cdn-script), read at show time:

AttributeDefaultDescription
data-fluid-toastoffSet to "true" to enable the built-in toast (events still fire either way)
data-fluid-toast-classβ€”Your class name on the toast element; style it (and its variants) in CSS
data-fluid-toast-positionbottom-centerbottom-center / bottom-left / bottom-right / top-center / top-left / top-right
data-fluid-toast-duration4000Auto-dismiss delay in milliseconds
data-fluid-toast-iconshownSet to "false" to hide the icon
data-fluid-toast-success-iconβœ“ iconCustom success icon β€” emoji, text, or SVG markup
data-fluid-toast-error-icon! iconCustom error icon β€” emoji, text, or SVG markup
data-fluid-toast-close-iconβœ•Custom close-button glyph or markup
data-fluid-toast-msg-addAdded to cartMessage for add operations
data-fluid-toast-msg-updateCart updatedMessage for update operations
data-fluid-toast-msg-removeRemoved from cartMessage for remove operations
data-fluid-toast-msg-errorSomething went wrong. Please try again.Message for any failure
<script
  id="fluid-cdn-script"
  src="https://assets.fluid.app/scripts/fluid-sdk/latest/web-widgets/index.js"
  data-fluid-shop="your-shop-id"
  data-fluid-toast="true"
  data-fluid-toast-class="my-toast"
  data-fluid-toast-position="top-right"
  data-fluid-toast-msg-add="Added! πŸŽ‰"
></script>

Styling

Give the toast one class with data-fluid-toast-class, then style it β€” and each variant β€” in CSS. The toast element exposes state as data attributes you can target:

AttributeValuesUse
data-variantsuccess / errorStyle success vs. error
data-positionthe configured positionPosition-specific tweaks
data-stateopen / closedEnter/exit styling
.my-toast {
  border-radius: 12px;
  font-family: var(--brand-font);
}
.my-toast[data-variant="success"] {
  background: #0f9d58;
}
.my-toast[data-variant="error"] {
  background: #d93025;
}

There is a single class hook by design β€” per-variant styling is done in CSS via data-variant, not with separate attributes.

Per-call suppression

Every mutation accepts { toast: false } to silence its toast for one call. The event still fires, so your own UI can react.

await window.FairShareSDK.addCartItems(11111, { quantity: 1, toast: false });

Button Loading Indicator

Add-to-cart interactions can show a spinner on the clicked button until the request settles.

Declarative (opt-in)

This is off by default. Add data-fluid-button-loading="true" to the Fluid script tag to make declarative data-fluid-add-to-cart / data-fluid-add-enrollment-pack buttons spin automatically β€” no per-button JavaScript needed. Add data-fluid-loading-text to swap the label while loading:

<!-- Enable the spinner globally -->
<script id="fluid-cdn-script" src="…" data-fluid-shop="…" data-fluid-button-loading="true"></script>

<!-- Any declarative button then spins on click -->
<button data-fluid-add-to-cart="11111" data-fluid-loading-text="Adding…">
  Add to cart
</button>

Can't edit the script tag? Enable it at runtime instead β€” FairShareSDK.configureCartFeedback({ buttonLoading: true }) (see above).

Custom calls

When you drive a mutation from your own JavaScript (custom payloads, bundles), wrap it so the spinner is managed for you. These helpers work regardless of the data-fluid-button-loading flag β€” that flag only controls the automatic declarative spinner above. withButtonLoading turns it on and clears it in a finally, whether the call resolves or throws:

button.addEventListener("click", (e) => {
  window.FairShareSDK.withButtonLoading(e.currentTarget, () =>
    window.FairShareSDK.addCartItems(11111, { quantity: 1 }),
  );
});

For full manual control, toggle it yourself (idempotent; also sets aria-busy):

const btn = e.currentTarget;
window.FairShareSDK.setButtonLoading(btn, true);
try {
  await window.FairShareSDK.addCartItems(11111, { quantity: 1 });
} finally {
  window.FairShareSDK.setButtonLoading(btn, false);
}
APIDescription
withButtonLoading(el, fn)Runs fn with the button loading; clears in finally. Returns/rethrows whatever fn does
setButtonLoading(el, on)Manually toggle the spinner. Idempotent; sets/clears aria-busy
data-fluid-loading-textOptional label shown on the button while loading

Behavior Note

addCartItems and decrementCartItem previously swallowed API failures and resolved undefined. They still resolve undefined on failure (their caller-visible contract is unchanged) β€” but now they also emit CART_OPERATION_ERROR, so the failure is no longer invisible. The other mutations continue to reject on failure while also emitting the error event.