Product Genius Docs

Your onAddToCart

The one piece you write — the signature, the Shopify variant-id mapping, and how to test it without the live feed.

Everything else on your side is configuration. onAddToCart is the one piece of behaviour you write: the feed calls it when a shopper clicks Add-to-Cart on a recommendation card, and you run your own cart mutation.

onAddToCart={async ({variantId, quantity}) => {
  await addToCart({variantId, quantity: quantity ?? 1}); // your cart mutation
  // …update your own cart UI/state here…
}}

addToCart above stands in for however your storefront adds a line item — a REST or GraphQL call, a wrapper client, or a framework helper. Each framework guide shows the idiomatic shape for that framework: a Next.js Server Action, a React Router action, a TanStack Start server function, or a plain client-side fetch.

The contract

type OnAddToCart = (item: {variantId: string; quantity: number}) => void | Promise<void>;
  • variantId — the id of the card's variant as identified in your Product Genius catalog, always a string.
  • quantity — how many to add. The examples here default it to 1 defensively; that costs nothing and keeps the handler correct if it is ever invoked without one.
  • The handler may be sync or async. Return a promise if you await your mutation.
  • You own the cart mutation and the resulting UI. Product Genius does not touch your cart, your cart state, or your drawer — it only tells you what was clicked.

You can pass a fresh inline arrow on every render; the feed component reads the latest handler through a ref, so it never re-injects the script or re-registers the handler.

onAddToCart only fires on stores configured for a headless cart — that routing is set on the Product Genius side by your contact. If cards render but Add-to-Cart buttons are missing, or clicking one does nothing, see the triage table.

Shopify variant ids

The one Product-Genius-specific line in most integrations.

Numeric id in, gid out

variantId arrives as the feed's numeric Shopify variant id. The Storefront API cart mutation wants a gid — gid://shopify/ProductVariant/<id>. Map it before you submit.

const merchandiseId = String(variantId).includes('gid://')
  ? String(variantId)
  : `gid://shopify/ProductVariant/${variantId}`;

The full worked version lives in the Hydrogen reference integration, which wires this through Hydrogen's CartForm:

app/routes/products.$handle.tsx (Hydrogen)
const cartFetcher = useFetcher({key: 'pg-add-to-cart'});
const {open} = useAside();

<ProductGeniusFeed
  /* …shop, pageConfig… */
  onAddToCart={({variantId, quantity}) => {
    // The feed sends a numeric variant id; the Storefront cart wants a gid.
    const merchandiseId = String(variantId).includes('gid://')
      ? String(variantId)
      : `gid://shopify/ProductVariant/${variantId}`;

    cartFetcher.submit(
      {
        [CartForm.INPUT_NAME]: JSON.stringify({
          action: CartForm.ACTIONS.LinesAdd,
          inputs: {lines: [{merchandiseId, quantity: quantity ?? 1}]},
        }),
      },
      {method: 'POST', action: '/cart'},
    );
    open('cart');
  }}
/>

CartForm comes from @shopify/hydrogen, useFetcher from react-router, and useAside is the Hydrogen skeleton's cart-drawer hook. The id mapping is the only Product Genius line in there; the rest is ordinary Hydrogen cart code.

Testing your handler

You do not need a live feed to test your cart handler. <ProductGeniusFeed> registers it at window.pgEventHandlers.onAddToCart, and that global is the test seam — invoke it exactly the way the feed does on a card click.

Unit — your handler is a plain function. Test it directly with its cart dependency mocked: assert it maps variantId correctly and issues the right cart mutation.

Integration — render the component, then call the registered handler:

import {act, render} from '@testing-library/react';
import {ProductGeniusFeed} from '@productgenius/react';

it('adds the variant to the cart', async () => {
  const onAddToCart = vi.fn();
  render(<ProductGeniusFeed shop="store.example.com" onAddToCart={onAddToCart} />);

  // what the feed does on an Add-to-Cart click:
  await act(async () => {
    await window.pgEventHandlers.onAddToCart({variantId: '123', quantity: 1});
  });

  expect(onAddToCart).toHaveBeenCalledWith({variantId: '123', quantity: 1});
});

If your handler performs a real request (a server action or route handler), intercept it with MSW and assert the mutation payload.

End-to-end — on a storefront whose Product Genius org is configured for a headless cart, load a product page, click a recommendation card's Add-to-Cart, and assert the cart updates. This exercises the live feed, so run it as a staging smoke test rather than a unit gate.

The same global doubles as a console one-liner for debugging a handler without clicking through the feed:

window.pgEventHandlers.onAddToCart({variantId: '123', quantity: 1});

How is this guide?

On this page