Add to cart
How the "Add to cart" button in the Humind widget works, platform by platform: what happens on Shopify, on PrestaShop, on WooCommerce, and how to wire it up on a custom storefront. For styling the button, see the widget settings; for the analytics events fired around the cart, see GA4 / GTM analytics.
How it works
Every product variant in your Humind catalog carries a cart action: a small instruction, stored with the variant, that tells the widget what to do when a shopper clicks "Add to cart". It is computed when your catalog syncs (Shopify app, managed feed) or when you push products through the Products API. The widget executes it blindly: it never needs to know which e-commerce platform powers your store.
There are four families of cart action:
| Family | What the shopper experiences |
|---|---|
Native (shopify, prestashop, woocommerce) | The widget calls your storefront's own cart endpoint from the shopper's browser, on your domain and with the shopper's session. The product lands in the real cart, your theme's mini-cart updates, and the shopper stays in the conversation. |
function | The widget calls a JavaScript add-to-cart function your storefront exposes on window (a quick-buy hook, for example injected via your tag manager). The widget confirms the add in the conversation; the shopper never leaves the chat. See custom storefronts. |
redirect | The widget sends the shopper to a URL of your choice: the product page, or any add-to-cart deep link your platform supports. Its target field says which of the two it is, and the CTA follows: "View product" for a product page, "Add to cart" for a deep link. |
noop | The variant is shown without an add-to-cart CTA. The button becomes "View product" and opens the product page inside the chat instead. Useful when checkout is gated (B2B) or the product requires configuration before purchase. |
After a successful native add, the widget:
- shows a confirmation toast with the product image and an optional "Go to checkout" shortcut,
- updates the cart badge in the chat header,
- pushes the
humind_product_added_to_cartevent into your dataLayer (see analytics).
A successful function add shows the same confirmation toast. Its "Go to checkout" shortcut opens the cart_url you set on the cart action (your cart or checkout page); without a cart_url the shortcut is hidden, since the widget has no cart of its own to open on a custom storefront. The cart badge only updates on platforms where the widget can read the cart.
Products with several purchasable variants open a variant picker first; the shopper picks one and the same flow runs for that variant. Products whose variants all use redirect with "target": "product_page" are the exception: the CTA links straight to the product page, where the shopper picks the variant on your own page.
Which family are you on?
Shopify merchants get native actions automatically. PrestaShop and WooCommerce merchants get them through the Products API, either explicitly or auto-derived. Everyone else picks function (in-conversation add via your own storefront hook), redirect or noop through the Products API.
Shopify
Nothing to configure: the Humind app syncs your catalog and attaches a native cart action to every variant.
- The widget posts to your storefront's standard
/cart/add.jsendpoint, on your domain, so the shopper's existing cart and session are used. - Multi-language stores are handled: the request follows your store's locale routes.
- After a successful add, the widget dispatches the standard
cart:updatedevent ondocument, which most themes (including Dawn and derivatives) listen to in order to refresh the cart drawer and cart icon. - The cart icon in the chat header sends the shopper to checkout by default. You can send them to the cart page instead with the
widget.cart_button_destinationsetting, or from your dashboard under AI agent, Chat interface. - Line items added from the chat are tagged with a
_humind_sourceline-item property, so you can identify them in your order data.
If the cart endpoint is unreachable on a given page (headless or hybrid setups), the widget falls back to opening the product page. See troubleshooting for the details.
PrestaShop
PrestaShop 1.7 and later is supported natively: the widget posts to your store's own cart controller, same-origin, using the CSRF token your theme already exposes. The native cart is updated in place and PrestaShop's standard updateCart event fires, so your theme's cart indicator refreshes.
Two ways to get PrestaShop cart actions on your variants:
- Auto-derivation (recommended): push your catalog through the Products API using PrestaShop's own ids as
external_id(id_productfor the product,id_product_attributefor the variant), and ask us to flag your account as PrestaShop. Every variant then gets a native cart action automatically. Make sureonline_store_urlis set on each product: it is required as the fallback destination. - Explicit
cart_action: push{ "type": "prestashop", "id_product": ..., "id_product_attribute": ..., "product_url": ... }on each variant. See the cart action reference.
If the shopper's page does not expose the PrestaShop front-office globals (PrestaShop 1.6, heavily customized themes), the widget falls back to a redirect to the product page.
WooCommerce
WooCommerce is supported natively through the Products API, with the same two paths as PrestaShop:
- Auto-derivation: push your catalog with WooCommerce's ids as
external_id(product_idfor the product,variation_idfor the variant,0when the product has no variations), and ask us to flag your account as WooCommerce.online_store_urlis required on each product. - Explicit
cart_action: push{ "type": "woocommerce", "product_id": ..., "variation_id": ..., "product_url": ..., "attributes": { ... } }. Theattributesmap carries theattribute_*values (for examplepa_color,pa_size) that variable products need.
The widget submits the standard add-to-cart form request to the product page, same-origin, then refreshes the theme's cart fragments so your mini-cart updates.
Variable products
Auto-derivation does not fill attributes. If your variable products require attribute parameters to add the right variation, push an explicit cart_action with the attributes map.
Custom storefronts and other platforms
On any other stack (Magento, BigCommerce, headless, in-house platforms), you have three options.
Call your own add-to-cart function
If your storefront exposes (or can expose) a JavaScript add-to-cart function, push a function cart action and the widget calls it directly, so shoppers add to cart without leaving the conversation:
{
"type": "function",
"name": "myQuickBuy",
"args": { "productId": "SKU123" },
"product_url": "https://www.your-store.com/p/sku123",
"cart_url": "https://www.your-store.com/cart"
}The contract on your side:
- Expose the function on
window(a global function, for example injected via your tag manager):window.myQuickBuy = async ({ productId, qty }) => { ... }. - When the shopper clicks "Add to cart", the widget calls it with a single object argument: your stored
argsplus aqtynumber (the quantity picked in the widget,1from chat CTAs). Ignore fields you don't recognize: the object may gain new optional fields over time (variant selection for example) without a breaking change. - Return a success signal: a Promise resolving
{ "success": true }(or simply nothing) on success, and{ "success": false, "errorName": "out_of_stock" }(or a rejected Promise) on failure. The widget uses it to record the add and its analytics events, and to tell the shopper what happened (see below). - The widget owns the confirmation UI: on success it shows its in-conversation confirmation toast (product image, title and quantity, plus a "Go to checkout" button that opens
cart_urlwhen you set one), so your function should not open its own modal or overlay. The chat window sits at a very highz-indexand is full-screen on mobile, so a page-level confirmation would be hidden behind it anyway. Update your mini-cart silently instead. - When the function is not present on the page (visitor outside an AB test bucket, excluded page, tag manager not loaded), the widget automatically falls back to redirecting the shopper to
product_url. No error, no dead button.
Field reference: cart action.
Naming a failure
errorName is optional, and any value keeps working: a name the widget does not recognize simply produces a generic message. Naming the reason is what lets the widget say something useful, in the shopper's own language, in every locale it supports.
These are the names it acts on:
errorName | What the shopper gets |
|---|---|
out_of_stock | The product has just gone out of stock. |
selection_required | No error message. The shopper is sent to product_url, because a size, a variant or an option can only be picked on your product page. If that URL is missing or is not http(s), the widget falls back to telling the shopper to pick their options on the product page. |
not_purchasable | The product cannot be bought on its own: gift with purchase, display-only item, bundle. |
product_not_found | The store does not know this product, or it went offline. |
max_quantity_reached | The shopper has hit your per-product or per-cart limit. |
add_refused | The store declined the add, without qualifying it further. |
context_not_allowed | Adding to the cart is not allowed on this page (checkout, order confirmation). |
integration_error | The call was outside the agreed contract. Never presented as the shopper's fault. |
network_error | A transport failure. The shopper is invited to retry. |
unknown_error | Anything else, and the fallback for unrecognized names. |
Return these names exactly as written (lowercase, underscores). Anything else is treated as unknown_error.
Watch selection_required and product_not_found
These two say something about the catalogue rather than the shopper. selection_required means we offered one-click buying on a product that needs a choice first, and product_not_found means our copy of your catalogue is behind yours. Both are worth telling us about: they are fixed on our side, at the sync, not on yours.
redirect
The widget navigates the shopper's tab to the URL you stored, exactly as if they had clicked a link. The conversation closes for that navigation and reopens where it left off on the destination page, as long as the widget is installed there.
{
"type": "redirect",
"url": "https://www.your-store.com/cart?add-to-cart=123",
"target": "add_to_cart"
}Two common targets, and target says which one you stored:
- The product page (
"target": "product_page"): zero risk, works everywhere. The shopper finishes the add to cart on your own product page. The widget labels the CTA "View product" and sends the shopper straight there, without opening a variant picker first. - An add-to-cart deep link (
"target": "add_to_cart"): a URL that, when opened, adds the item to the cart and shows the cart, so the shopper skips the product page. The widget labels the CTA "Add to cart". Any GET URL your stack exposes for this works; platforms whose cart endpoint requires a per-session CSRF token cannot offer a stable URL, in which case redirect to the product page or use afunctionaction instead.
Set target when your URL is a product page
Both destinations are valid and we cannot tell them apart from the URL, so an omitted target means add_to_cart and the button reads "Add to cart". Leaving it out on a product-page URL gives the shopper a cart CTA that lands them on your page with nothing added. The default errs this way on purpose: the opposite mistake, a "View product" button that silently fills the cart, is the one shoppers cannot recover from.
What the URL must look like
- An absolute URL including the scheme and domain:
https://www.your-store.com/cart?add-to-cart=123. Only the shorthand without a domain (/cart?add-to-cart=123) is rejected, because the API cannot guess which domain to prepend. Query parameters are unrestricted: add as many as you need, including tracking parameters or a URL-encoded URL inside a parameter. http://orhttps://only.javascript:,data:and every other scheme are rejected.- Any domain is allowed. The URL does not have to be on the domain where the widget is installed: pointing at a checkout funnel, a landing page or a booking flow hosted on another domain is a supported use case. Two things to keep in mind when you leave your storefront domain: the shopper's existing cart (stored in cookies on your storefront domain) does not carry over unless your funnel handles that itself, and the conversation only reopens on the destination page if the widget is installed there too.
- The URL is stored per variant and used as-is: the widget appends nothing to it. In particular, the quantity selected in the widget is not carried over; the deep link adds whatever quantity the URL itself encodes (usually 1).
noop
Show the product with a "View product" button only.
All three are set per variant through the Products API. redirect and noop work with zero code on your side; function needs the one function above. If you are unsure which fits your stack, contact us.
Measuring cart activity
Independent of the platform, every add and remove initiated from the widget is:
- pushed to your
window.dataLayerashumind_product_added_to_cart/humind_product_removed_from_cart(withproduct_id,product_variant_id,price), ready to forward to GA4'sadd_to_cartevent from your tag manager: see GA4 / GTM analytics, - recorded in your Humind analytics, powering the conversion and revenue reporting in your dashboard.