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:
| Function | Category | On success | On failure |
|---|---|---|---|
| addCartItems | add | CART_OPERATION_SUCCESS | CART_OPERATION_ERROR (still resolves undefined) |
| addEnrollmentPack | add | CART_OPERATION_SUCCESS | CART_OPERATION_ERROR (rethrows) |
| updateCartItems | update | CART_OPERATION_SUCCESS | CART_OPERATION_ERROR (rethrows) |
| decrementCartItem | update | CART_OPERATION_SUCCESS | CART_OPERATION_ERROR (still resolves undefined) |
| updateCartItemVariant | update | CART_OPERATION_SUCCESS | CART_OPERATION_ERROR (rethrows) |
| removeCartItemById | remove | CART_OPERATION_SUCCESS | CART_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:
| Field | Type | When | Description |
|---|---|---|---|
operation | string | always | The function that ran, e.g. "addCartItems" |
category | "add" | "update" | "remove" | always | Coarse bucket used to pick the default toast message |
toast | boolean | always | false when the call passed { toast: false } β the built-in toast skips it |
timestamp | string | always | ISO timestamp |
cart | Cart | success | The resulting cart, when the operation returned one |
error | unknown | error | The raw thrown value |
message | string | error | Best-effort human-readable message |
variantIds | number[] | when known | Variant IDs the operation acted on |
itemId | number | when known | Cart item ID (for item-scoped operations) |
enrollmentPackId | number | when known | Enrollment 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:
| Attribute | Default | Description |
|---|---|---|
data-fluid-toast | off | Set 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-position | bottom-center | bottom-center / bottom-left / bottom-right / top-center / top-left / top-right |
data-fluid-toast-duration | 4000 | Auto-dismiss delay in milliseconds |
data-fluid-toast-icon | shown | Set to "false" to hide the icon |
data-fluid-toast-success-icon | β icon | Custom success icon β emoji, text, or SVG markup |
data-fluid-toast-error-icon | ! icon | Custom error icon β emoji, text, or SVG markup |
data-fluid-toast-close-icon | β | Custom close-button glyph or markup |
data-fluid-toast-msg-add | Added to cart | Message for add operations |
data-fluid-toast-msg-update | Cart updated | Message for update operations |
data-fluid-toast-msg-remove | Removed from cart | Message for remove operations |
data-fluid-toast-msg-error | Something 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:
| Attribute | Values | Use |
|---|---|---|
data-variant | success / error | Style success vs. error |
data-position | the configured position | Position-specific tweaks |
data-state | open / closed | Enter/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); }
| API | Description |
|---|---|
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-text | Optional 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.
Related
- Cart Overview β all cart operations
- addCartItems β add one or more items
- addEnrollmentPack β add an enrollment pack
- Installation β the Fluid script tag and its attributes