Skip to main content
Location availability lets customers check which stores or warehouses have a product in stock. This is especially useful for businesses with retail locations where customers might want to buy in-person or choose click-and-collect.

No-code setup

The simplest approach is to use the built-in Stockful Location Availability theme block:
  1. In the theme editor, navigate to your product template
  2. Click Add block > Apps > Stockful Location Availability
  3. Configure the button text, style, and maximum locations to show
This adds a button that opens a slide-out drawer with live stock data. See the theme blocks guide for details on settings.

Custom implementation

For full control over the UI, use the window.stockful JavaScript API.

Basic location list

<button id="check-stores" type="button">Check store availability</button>
<div id="store-list" style="display: none; margin-top: 1em;"></div>

<template id="store-row">
  <div style="display: flex; justify-content: space-between; padding: 0.6em 0; border-bottom: 1px solid #eee;">
    <div>
      <div data-name style="font-weight: 500;"></div>
      <div data-city style="font-size: 0.85em; opacity: 0.6;"></div>
    </div>
    <div data-status></div>
  </div>
</template>

<script>
  const btn = document.getElementById("check-stores");
  const container = document.getElementById("store-list");
  const template = document.getElementById("store-row");

  btn.addEventListener("click", async () => {
    const variantId = new URLSearchParams(window.location.search).get("variant");
    if (!variantId || !window.stockful) return;

    container.style.display = "block";
    container.replaceChildren(Object.assign(document.createElement("p"), { textContent: "Loading..." }));

    const locations = await window.stockful.getLocationAvailability(Number(variantId));

    if (locations.length === 0) {
      container.replaceChildren(Object.assign(document.createElement("p"), { textContent: "No store data available." }));
      return;
    }

    const fragment = document.createDocumentFragment();

    for (const loc of locations) {
      const row = template.content.cloneNode(true);
      const inStock = loc.available > 0 || loc.status === "in_stock";

      row.querySelector("[data-name]").textContent = loc.name;

      const city = row.querySelector("[data-city]");
      if (loc.city) {
        city.textContent = loc.city;
      } else {
        city.remove();
      }

      const status = row.querySelector("[data-status]");
      status.style.color = inStock ? "#16a34a" : "#dc2626";
      status.textContent = loc.available != null
        ? (inStock ? `${loc.available} available` : "Out of stock")
        : (inStock ? "In stock" : "Out of stock");

      fragment.appendChild(row);
    }

    container.replaceChildren(fragment);
  });
</script>
If you prefer a modal over an inline list, combine the location data with a Shopify ui-modal or your own modal component:
<button id="open-locations" type="button">Check store availability</button>

<template id="modal-row">
  <div style="display: flex; justify-content: space-between; padding: 0.5em 0; border-bottom: 1px solid #f0f0f0;">
    <span data-name></span>
    <span data-status></span>
  </div>
</template>

<dialog id="locations-modal" style="
  border: none;
  border-radius: 12px;
  padding: 1.5em;
  max-width: 450px;
  width: 90vw;
  box-shadow: 0 10px 40px rgba(0,0,0,0.15);
">
  <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1em;">
    <h3 style="margin: 0;">Store Availability</h3>
    <button id="close-locations" type="button" style="background: none; border: none; font-size: 1.2em; cursor: pointer;">&times;</button>
  </div>
  <div id="modal-locations"></div>
</dialog>

<script>
  const modal = document.getElementById("locations-modal");
  const content = document.getElementById("modal-locations");
  const template = document.getElementById("modal-row");

  document.getElementById("open-locations").addEventListener("click", async () => {
    modal.showModal();
    content.replaceChildren(Object.assign(document.createElement("p"), { textContent: "Loading..." }));

    const id = new URLSearchParams(window.location.search).get("variant");
    if (!id || !window.stockful) return;

    const locations = await window.stockful.getLocationAvailability(Number(id));
    if (locations.length === 0) {
      content.replaceChildren(Object.assign(document.createElement("p"), { textContent: "No store data available." }));
      return;
    }

    const fragment = document.createDocumentFragment();

    for (const loc of locations) {
      const row = template.content.cloneNode(true);
      const inStock = loc.available > 0 || loc.status === "in_stock";

      row.querySelector("[data-name]").textContent = loc.name;

      const status = row.querySelector("[data-status]");
      status.style.color = inStock ? "#16a34a" : "#dc2626";
      status.textContent = loc.available != null
        ? (inStock ? `${loc.available} available` : "Out of stock")
        : (inStock ? "In stock" : "Out of stock");

      fragment.appendChild(row);
    }

    content.replaceChildren(fragment);
  });

  document.getElementById("close-locations").addEventListener("click", () => modal.close());
</script>

Controlling visibility

Visible locations are controlled in the Stockful app under Settings > Storefront - choose which locations appear to customers (e.g. hide your main warehouse, show only retail stores). This applies to both the built-in theme block and the window.stockful API. Show available quantity is a per-block setting on the theme block. When enabled, customers see “42 available”; when disabled, they see “In stock” / “Out of stock”. For custom implementations using the JS API, you control this in your own code.

Tips

  • Retail stores only - most merchants hide warehouses and fulfillment centers, showing only locations where customers can visit
  • Quantities vs. status - showing exact quantities can be useful for high-demand items but may seem odd for regular products. Status labels (“In stock” / “Out of stock”) are a safer default
  • Live data - location availability is fetched live from Shopify (not from cached metafields), so it’s always up to date. The trade-off is a slightly longer load time compared to metafield-based data
  • Cache duration - location data is cached for 60 seconds (vs 30 seconds for variant/product data), so very recent inventory changes may take up to a minute to appear