Home
Headless & Hydrogen

Headless & Hydrogen

Run STOQ preorders and back-in-stock on a headless storefront with the Storefront SDK — a script tag for any stack, React hooks for Hydrogen.

STOQ's storefront widgets ship as a Shopify theme app embed, which doesn't load on headless storefronts (Hydrogen, Next.js Commerce, custom builds). The STOQ Storefront SDK brings the same capabilities to those stacks: preorder state per variant, cart lines with the right selling plan attached, and back-in-stock signups — including the notify-me modal.

📦 npm: @artossoftware/stoq-sdk — framework-free, fully typed, SSR-safe (nothing touches window/document at import), zero runtime dependencies. Setup is two fields: your shop domain and its public Storefront API access token (the same token your headless store already uses). It loads the merchant's STOQ config once at init and fetches per-variant availability on demand (batched + cached) from your shop's own Storefront API on Shopify's edge.

Note

Beta. Both install paths are live: npm install @artossoftware/stoq-sdk, or the script tag below (served from jsDelivr's CDN, pinned to the current major). Feedback: support@stoqapp.com.

There are three ways to integrate, from least to most code:

PathForYou write
Script tagAny headless stack, no build stepOne <script> tag + a few lines of JS
npm + React hooksHydrogen / React 18+ storefrontsComponents using our hooks
Raw APIsAnything else / full controlYour own client against our metafields + the signup endpoint

1. Script tag (any stack)

Add one tag to your <head>. It identifies your shop by domain — no credentials go in your HTML, and your admin API key is never used in the storefront:

<script
  src="https://cdn.jsdelivr.net/npm/@artossoftware/stoq-sdk@0/dist/stoq.min.js"
  data-shop="your-shop.myshopify.com"
  data-storefront-token="your-public-storefront-api-token"
  defer
></script>
Note

data-storefront-token is your public Storefront API access token — the same one your headless store already uses (public tokens are designed to be exposed in browser code; never use a private Storefront API token here). With it, the SDK reads everything from Shopify's edge-cached Storefront API — fast, close to your shoppers, and as fresh as Shopify itself.

The script defines a global Stoq, initializes automatically, and fires stoq:loaded when ready:

<script>
  window.addEventListener('stoq:loaded', async () => {
    // Ask by id — the SDK fetches the variant's availability itself
    // (batched + cached). If you already have the variant object, pass
    // { id, availableForSale, currentlyNotInStock } to skip that lookup.
    const state = await Stoq.client.getVariantState(42424242424242)

    if (state.isPreorder) {
      // Show your preorder UI; state.shippingText has the estimated-shipping copy.
      // Add to cart with state.sellingPlanGid (see "Carts" below).
    } else if (Stoq.client.signupsEnabled /* && your data says it's sold out */) {
      // One call opens the STOQ notify-me modal — form, validation,
      // success states and styling come from your STOQ design settings:
      Stoq.openModal({ variantId: 42424242424242, productTitle: 'Classic Tee' })
    }
  })
</script>

Optional attributes: data-market (Shopify Market id for market-scoped offers) and data-locale. Without data-shop the script only defines the global — call Stoq.init({...}) yourself.

The modal can also render as an inline form inside any element you supply: Stoq.openInlineForm({ container: '#notify-me', variantId }). Texts and colors default to your dashboard design settings and accept per-call overrides.

2. React / Hydrogen

npm install @artossoftware/stoq-sdk

Wrap your app (or just the product route) in the provider, then use the hooks. Pass the public Storefront API token your Hydrogen app already ships with — the PUBLIC_STOREFRONT_API_TOKEN env var (the same value createStorefrontClient uses as publicStorefrontToken):


export default function App() {
  return (
    <StoqProvider
      config={{
        shop: 'your-shop.myshopify.com',
        storefrontToken: env.PUBLIC_STOREFRONT_API_TOKEN,
      }}
    >
      <Outlet />
    </StoqProvider>
  )
}

A complete add-to-cart button that handles preorders automatically — the line input from useStoqCartLine already carries sellingPlanId when (and only when) the variant should be sold as a preorder, so it passes straight into Hydrogen's CartForm:


function BuyBox({ variant }) {
  // Pass the variant object itself (include availableForSale and
  // currentlyNotInStock in your variant fragment) — its availability fields
  // decide preorder state. GIDs are accepted directly.
  const state = useStoqVariant(variant)
  const line = useStoqCartLine(variant, 1)

  if (!line) return null // SDK still loading (or SSR)

  if (!variant.availableForSale && !state?.isPreorder) {
    return <StoqNotifyMeButton variantId={variant.id} productId={variant.product.id} />
  }

  return (
    <CartForm route="/cart" action={CartForm.ACTIONS.LinesAdd} inputs={{ lines: [line] }}>
      <button type="submit">{state?.isPreorder ? 'Preorder now' : 'Add to cart'}</button>
      {state?.isPreorder && state.shippingText ? <p>{state.shippingText}</p> : null}
    </CartForm>
  )
}

Available from @artossoftware/stoq-sdk/react:

ExportWhat it does
useStoqVariant(variant){ isPreorder, sellingPlanId, shippingText, maxCount, remainingCount }null while loading
useStoqCartLine(variant, qty?)Storefront Cart API CartLineInput, with sellingPlanId only when preorderable
useStoqSignup(){ submit, submitting, result, error } for building your own notify-me UI
useStoq()Raw { client, status, refresh } access
<StoqNotifyMeButton>Button → inline email form → signup; minimal overrideable default styling (stoq-* classnames + --stoq-* CSS variables)
<StoqPreorderBadge>Renders children (default "Preorder") only when the variant is preorderable

Everything is SSR-safe — importing never touches window, and components render nothing on the server.

The vanilla core (import { init } from '@artossoftware/stoq-sdk') is the same engine without React — use it from Vue, Svelte, or plain JS with the identical getVariantState / cartLineFor / createSignup API.

How data loads: all reads come from Shopify

init({ shop, storefrontToken }) makes ONE GraphQL call to your shop's own edge-cached Storefront API for the merchant's STOQ configuration (offers, texts, limits), and per-variant availability is looked up on demand when you ask about a variant (batched — concurrent lookups share one request — and cached for 60 seconds). There are no catalog-wide inventory lists to download, and reads are served from Shopify's edge — fast and as fresh as Shopify itself.

Already have the variant object from your own product query? Pass it ({ id, availableForSale, currentlyNotInStock }) instead of a bare id and the availability lookup is skipped — your (possibly @inContext-localized) data is used verbatim.

Signups post to STOQ (it's a write into our system) — that's the SDK's only STOQ call, it only fires on actual signups, and like all our APIs it is subject to rate limits.

Carts: how preorders attach

On headless storefronts, carts are Storefront API carts, and preorders work by attaching a selling plan to the line. The SDK hands you the finished line input:

mutation cartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
  cartLinesAdd(cartId: $cartId, lines: $lines) {
    cart { id totalQuantity }
  }
}
{
  "cartId": "gid://shopify/Cart/...",
  "lines": [{
    "merchandiseId": "gid://shopify/ProductVariant/42424242424242",
    "sellingPlanId": "gid://shopify/SellingPlan/3232323232",
    "quantity": 1
  }]
}

That's all a preorder is at checkout time — STOQ's backend takes over from the order webhook onward (payment scheduling, fulfillment holds, tagging).

Events

The SDK fires the same custom events as the theme embed (on window), so analytics and integrations behave identically:

EventWhen
stoq:loadedSDK initialized
stoq:restock-modal:opened / closedModal lifecycle
stoq:restock-modal:submittedSignup created (modal, inline form, or createSignup)

Behavior notes

  • Bare ids work everywhere — the SDK fetches availability itself. Passing your variant object (availableForSale, currentlyNotInStock) skips that lookup and uses your @inContext-localized data verbatim, which is the most precise option for multi-market stores (or set country in the config).
  • Gate notify-me UI on your own availability data (!variant.availableForSale) plus client.signupsEnabled.
  • Offer configuration can be briefly stale. The metafields STOQ writes are re-synced on merchant saves, Shopify's edge may cache reads, and the SDK session-caches for 5 minutes per shop+market — call refresh() after operations that change offers. Availability is fresher: a 60-second cache, or exactly your own data when you pass the variant object.

3. No SDK: raw APIs & metafields

Everything the SDK does is built on public surfaces you can use directly:

  • Back-in-stock signupsPOST /api/v1/intents.json is CORS-open and unauthenticated (shop identified by domain): see Create an Intent.
  • Variant/offer state via Storefront API metafields — STOQ's shop- and variant-level metafields (namespace restockrocket_production: cachedSellingPlans, preorder_variant_ids, variant shipping_text, preorder_max_count, …) are exposed with PUBLIC_READ storefront access, so any Storefront API client — including a Hydrogen loader running server-side — can read preorder state with zero STOQ API calls:
query ProductPreorderState($handle: String!) {
  shop {
    sellingPlans: metafield(namespace: "restockrocket_production", key: "cachedSellingPlans") { value }
  }
  product(handle: $handle) {
    variants(first: 50) {
      nodes {
        id
        shippingText: metafield(namespace: "restockrocket_production", key: "shipping_text") { value }
        preorderMaxCount: metafield(namespace: "restockrocket_production", key: "preorder_max_count") { value }
      }
    }
  }
}
  • Admin operations (creating offers, releasing fulfillments, reports) — the v2 API, server-side only.
Warning

Never ship X-Auth-Token (your admin API key) to the client. It grants full read/write access to your STOQ data. The SDK never uses it — storefront calls are identified by shop domain only.