Skip to main content
The SDK’s reactive state is built on framework-agnostic signals. Every controller exposes three ways to consume state changes — you can pick whichever fits your stack:
  1. controller.subscribe(callback) — built-in, no imports needed
  2. subscribe(signal, callback) — standalone utility for any signal
  3. effect(() => { ... }) — direct signal access for custom reactivity
All examples below use a collection controller, but the same patterns apply to search, blocks, suggest, searchContent, and createProductCard.

Vanilla JavaScript

Use subscribe to drive DOM updates directly:

React

Wrap the SDK’s subscribe in a hook using React’s useSyncExternalStore for tear-free reads:
Usage:

Vue

Use Vue’s ref and onUnmounted to bridge SDK state:

Svelte

Use the subscribe callback to drive Svelte’s reactive $state:

Lit

Use subscribe in connectedCallback / disconnectedCallback:

Alpine.js

Alpine pairs well with the SDK in zero-build Shopify themes — boot the client from a CDN, then use an Alpine component to expose the controller state to your template. Hand the controller’s subscribe callback Alpine’s reactive $data and the DOM updates automatically as the SDK signal emits.

Boot the client once

Load the SDK and Alpine from a CDN, then create the client in a single <script type="module"> block in theme.liquid (or any layout). See Loading from a CDN for alternative CDNs and version pinning.

Collection component

Define an Alpine component that owns a client.collection() controller. The init() lifecycle hook creates the controller, wires subscribe into Alpine’s reactive state, and triggers the initial execute(). destroy() tears the subscription and the controller down so navigating away never leaks signals.
The component reads data.products, data.page, and data.totalPages straight off the SDK state and exposes them as Alpine reactive properties — every signal emit re-renders the matching x-text, x-for, and x-show bindings.

Search component

The same pattern works for search. Reuse client.suggest() and client.search() so you get debouncing, abort handling, and the search_id prepare/execute lifecycle for free.

Store for sharing controllers across components

If multiple Alpine components on the same page need the same controller (a header search box that drives a results grid, for example), wrap the controller in an Alpine store so every component subscribes to the same signal.
Alpine evaluates x-data expressions inside with blocks, so accessing data?.priceRange?.formatted works just like it would in plain JS. Use optional chaining everywhere you read SDK state — the controller emits a state with data: null while the first request is in flight.

Using standalone subscribe

The SDK exports a subscribe utility that works with any signal, not just controller state. This is useful for watching computed values or composing signals:

Using effect directly

For maximum flexibility, use effect to react to any combination of signals:

ProductCard integration

The createProductCard controller follows the same patterns. Here’s a React example: