Shopify Builds>Three Ships>Shoppable-video to cart bridge: Hue add-to-cart event wired into Rebuy's drawer

Shoppable-Video to Cart Bridge: Hue Add-to-Cart Event Wired Into Rebuy's Drawer

A document-level listener that turns a shoppable-video app's add-to-cart intent into a real cart/add.js call with the cart sections rendered in the same response, then dispatches the theme's own cart events so the drawer, the count badge and every other subscriber update at once.

A shoppable-video app can announce that a shopper tapped "add" inside a video, but it can't add to a Shopify cart it doesn't own or update a drawer another app renders. This listener catches Hue's hue-add-to-cart event, posts to cart/add.js with sections requested so the cart markup returns in the same response, then fires the theme's own cart:build and cart:refresh events so every listener updates from one round trip.

The Problem

Shopify has one cart per storefront and many things that want to change it. A shoppable-video app embeds a player with a product card and an "add" button, but the app runs inside its own script and does not know how this particular theme adds to cart, what the drawer looks like, or which events the rest of the page listens to. So the app does the only honest thing it can: it fires a DOM event saying "the shopper wants this variant," and stops.

On this storefront, that event was landing in silence. A shopper watching a video on a landing page, tapping "add" on the product it featured, saw nothing happen — no drawer, no count change, no line item. The intent was being emitted; nothing was catching it.

The Constraint

Three runtimes had to agree, and none of them could be changed. Hue, the shoppable-video app, fires a hue-add-to-cart event carrying a variantId and nothing more — no quantity, no callback, no knowledge of the cart. Shopify's Cart AJAX API accepts the add but does not know the drawer exists. And Rebuy owns the visible cart on this store: its drawer is what the shopper sees, and it keeps its own copy of cart state.

No vendor was going to alter its behavior for this store, so the bridge had to be theme code — and theme code that lives where the video units are placed. Those units sit on landing pages built in Shogun, which renders through its own layout file rather than the theme's default one. The listener had to be mounted in that layout, or it would never be on the page that needed it.

What We Built

Roughly 35 lines of JavaScript in theme.shogun.landing.liquid, the layout Shogun-built landing pages render through.

Catch the intent. A document-level listener subscribes to hue-add-to-cart. Because it is bound to the document rather than to a container, a video unit placed anywhere on the landing page — top, middle, inside a Shogun column — reaches it, and the event's detail is the only input the bridge needs: the variantId Hue attaches when the shopper taps the in-video product card.

Perform the add, and ask for the markup. The listener builds a FormData with the event's variantId and a quantity of one, and appends sections: 'cart,cart-count'. It posts that to window.Shopify.routes.root + 'cart/add.js'. That sections parameter is the Section Rendering API riding on the Cart AJAX API: the same request that mutates the cart returns, in its JSON response, the freshly rendered HTML of the cart drawer section and the header count section, reflecting the line item that was just added. One request. No second fetch to find out what the cart now contains.

Tell the page. On success the bridge opens the cart, then dispatches two bubbling CustomEvents on the document: cart:build, and cart:refresh carrying the parsed response as its detail. Those two events are the theme's own cart-update contract — the same events the theme's cart code dispatches after its own adds — so every listener already subscribed to them, including the drawer body and the count badge, updates from this add exactly as it would from a native one. The bridge never touches cart markup itself; it performs the add, carries the rendered sections, and lets the theme's existing subscribers do what they already do.

Why This Way

Requesting sections on the add call means the theme's cart markup is refreshed from the request that performed the mutation. The alternative — post the add, then fetch cart.js, then re-render — is two round trips and a window where the count badge is wrong.

Dispatching the theme's native events rather than calling into its cart functions is what keeps the bridge decoupled from the cart implementation. If the theme's cart code is refactored, the bridge does not care, as long as cart:build and cart:refresh still mean what they mean. The bridge speaks the contract, not the internals.

The cost is scope. The listener lives in the Shogun landing layout only. A video unit placed on a standard page template, or on a product page, has no bridge — Hue's event would fire there and land in the same silence it did before. We put the code where the video units were, rather than in theme.liquid for every page, because the bridge is a seam between two specific apps and a global listener would run on every page for an event that fires on a handful. If the video units move, the bridge has to move with them.

Why Not an App

This is the seam between two apps, and neither vendor sells it. Hue's job ends at emitting the event; Rebuy's job starts at rendering a cart it believes is current. The gap between "the shopper wanted this" and "the drawer shows it" belongs to the theme, because the theme is the only party on the page that knows both the cart routes and the drawer's expectations.

A third app to broker between two apps is a real category — cart-sync and event-bridge utilities exist — but it adds a fourth runtime to a problem that already had three, and it would still have to be told this theme's event names. Thirty-odd lines in the layout that already hosts the video units is the smaller thing.

Implementation Notes

  • The add posts to window.Shopify.routes.root + 'cart/add.js' rather than a hard-coded /cart/add.js, so the bridge survives a locale-prefixed storefront root.
  • The request body is FormDataid, quantity, sections — not JSON, matching what the theme's own product form sends.
  • sections: 'cart,cart-count' names the drawer section and the header count section by their section ids, so the response carries both rendered.
  • cart:build and cart:refresh are dispatched with bubbles: true, so a listener registered above the document node — on window, for instance — still receives them.
  • cart:refresh carries the parsed add response as its detail, so subscribers that want the line item or the rendered sections get them without a second request.
  • The listener lives in theme.shogun.landing.liquid, which means it is active precisely on the Shogun-built landing pages where the video units are placed.

Edge Cases

  • Requesting sections: 'cart,cart-count' covers the drawer body and the header count badge in one round trip, so the two never disagree about how many items are in the cart.
  • A storefront root with a locale prefix still resolves the correct add route, because the path is built from Shopify.routes.root rather than assumed.
  • Quantity is fixed at one per event, because Hue's event carries a variantId and no quantity. A shopper who wants two taps twice, and cart/add.js increments the existing line rather than creating a second one.
  • A listener registered on window rather than document still receives both events, because they bubble past the document node.

Platform Primitives Used

  • cart-ajax-api — the add is a plain cart/add.js POST with a FormData body, the same route the theme's own product form uses.
  • section-rendering-api — the sections parameter on the add request returns the drawer and count sections re-rendered in the same response.

Integrations in Play

  • Hue — the shoppable-video app whose hue-add-to-cart event is the bridge's input. It renders the video and the in-video product card; it does not touch the cart.
  • Rebuy — owns the visible cart drawer on this storefront. The bridge coexists with it and brings the drawer into step after an add it did not perform.
  • Shogun — the page builder whose landing layout hosts the listener; the video units live on Shogun-built pages.

Where It Runs

Landing pages built in Shogun, through the page.shogun.landing template and the layout it renders through. The effect lands on the cart — the drawer and the header count — but the code itself runs only on the pages where the video units are placed, and nowhere else on the storefront.

What This Demonstrates

How We Know

One layout file from the theme — the bridge is the inline script inside it — plus two task-register entries from client working sessions. Those entries record shoppable-video work on the same landing surface, including a display fix to the video player at a mobile breakpoint; they establish that the video units and this landing layout were an active piece of work, not that this bridge was specified in those words. The event name, the sections request and the theme-event dispatch are read from the code.

Related Builds

The Buy-vs-Build Question

Shoppable video is almost always a buy — the player, the hosting, the in-video product card are not worth writing. The part that is not for sale is the seam between the player and your cart, and it is small enough that owning it is the obvious call. Where the line sits between the player you buy and the integration you own is the subject of the shoppable video and media decision page.

Provenance & Evidence

  • Client: Three Ships — threeshipsbeauty.ca
  • Surfaces: Landing pages; effect on the cart
  • Templates served: one — page.shogun.landing
  • Complexity: Medium
  • Attribution: Deploi-authored. The bridge is ours. It runs inside a layout on Palo Alto 5.8.0 by Presidio Creative, a paid premium theme the client licenses, and it speaks to two vendor apps — Hue and Rebuy — that we did not write.
  • Status: Live, verified 2026-09-06
  • Evidence: One layout file from the theme, plus two task-register entries from client working sessions
  • Confidence: Strong — the code is unambiguous; the client record places the work on this surface
  • Primary capability: Video and shoppable media modules

Got a Widget Whose Add Button Does Nothing?

You dream it. We build it. If two apps on your storefront each think they own the cart, Contact us today and we'll write the seam between them.

More builds