Articles on: API

Use STOQ Javascript API to build custom 'Notify me' & Preorder experiences

STOQ JavaScript API - Complete Guide for Custom Integrations


STOQ provides a powerful JavaScript API that gives you complete control over how back-in-stock and preorder features appear on your storefront. Perfect for developers who want to build custom experiences that match their unique brand and user flows. Access everything through the window._RestockRocket object when the app loads.


For complete documentation, detailed parameters, request/response examples, and interactive testing, visit docs.stoqapp.com/javascript-api **and docs.stoqapp.com/custom-events


What you can build

  • Custom 'Notify me' buttons styled to match your brand
  • Custom 'Preorder' widget for the ideal customer experiences
  • Inline signup forms embedded anywhere
  • Dynamic preorder badges and information
  • Multi-variant selection experiences
  • Analytics tracking with custom events



Checking if STOQ JS API is available


Listen for the stoq:loaded event before using the API:


window.addEventListener('stoq:loaded', (event) => {
const { pageType, enabled, settings } = event.detail;

if (enabled) {
console.log('STOQ is ready on', pageType);
// Safe to use window._RestockRocket methods
}
});


Page types: 'product', 'collection', 'index', 'search', 'page'



Available Methods



Method

Product Page

Collection/Home/Other Pages

openModal

openInlineForm

removeInlineForm

renderButtonForVariant

getSellingPlan + preorder methods





1. openModal


Opens the notification modal for a specific product variant. This is your go-to method for triggering STOQ's signup experience from anywhere on your site - custom buttons, quick-add features, collection grids, you name it. The modal handles all the heavy lifting: form validation, duplicate detection, and submission.


window._RestockRocket.openModal(productData, variantId, customerData)


Parameters:

  • productData (Object, required):
  {
id: 123456789, // Shopify product ID
title: "Cool T-Shirt",
handle: "cool-t-shirt",
variants: [...] // Product variants array
}
  • variantId (String, required): Variant ID to show modal for
  • customerData (Object, optional): Pre-fill customer info
  {
email: "customer@example.com",
phone: "+1234567890",
name: "Jane Doe"
}


Examples:


On Collection Page:

const addToCartBtn = document.getElementById('quick-add-btn');
addToCartBtn.addEventListener('click', () => {
const productData = {
id: 1111,
variants: [{ id: 2222, available: false }]
};
const variantId = '41XX';

window._RestockRocket.openModal(productData, variantId);
});


On Product Page (Simple):

const notifyMeBtn = document.getElementById('notify-me-btn');
notifyMeBtn.addEventListener('click', () => {
// Opens modal for current product automatically
window._RestockRocket.openModal();
});


With Liquid:

<button onclick="window._RestockRocket.openModal({{ product | json }}, '{{ product.selected_or_first_available_variant.id }}')">
Notify Me
</button>




2. openInlineForm


Embed the signup form directly into your page layout instead of showing it in a modal. Perfect for creating seamless, native-feeling experiences where you want the form to feel like part of your page rather than a popup. You control exactly where it appears and how it integrates with your design.


window._RestockRocket.openInlineForm(productData, variantId, customerData, inlineFormContainer, inlineFormContainerInsertType)


Parameters:

  • Same as openModal plus:
  • inlineFormContainer (String, optional): CSS selector for container
  • inlineFormContainerInsertType (String, optional): Where to insert - 'beforebegin', 'afterbegin', 'beforeend' (default), 'afterend'


Example:


<div id="custom-notify-form"></div>

<script>
const addToCartBtn = document.getElementById('quick-add-btn');
addToCartBtn.addEventListener('click', () => {
const productData = { id: 1111, variants: [{ id: 2222, available: false }] };
const variantId = '41XX';

window._RestockRocket.openInlineForm(
productData,
variantId,
null, // customerData (optional)
'#custom-notify-form', // Container selector
'afterend' // Insert position
);
});
</script>




3. removeInlineForm


Removes the inline form from the page.


window._RestockRocket.removeInlineForm()


Example:

// Remove form when variant changes or form is submitted
window._RestockRocket.removeInlineForm();




4. renderButtonForVariant


Automatically render the appropriate button (notify-me or preorder) for a specific variant. This method is smart - it checks inventory levels and preorder settings to show the right call-to-action. Great for themes with custom variant selectors where you want the button to update dynamically as customers choose options.


window._RestockRocket.renderButtonForVariant(variantId, targetElement)


Parameters:

  • variantId (String, required): ID of the variant
  • targetElement (String, optional): CSS selector for container


Example:


// Update button when variant selector changes
const variantSelector = document.getElementById('variant-selector');
variantSelector.addEventListener('change', (event) => {
const selectedVariantId = event.target.value;
window._RestockRocket.renderButtonForVariant(selectedVariantId);
});




5. Preorder Methods


STOQ exposes a comprehensive set of methods for building custom preorder experiences. Whether you're displaying availability counts, shipping dates, or building complex multi-payment-option flows, these methods give you full control.


Core Preorder Methods


Method

Description

Returns

getSellingPlan(variantId)

Get preorder config for a variant

Object or null

getPreorderAvailable(variantId)

Get available preorder quantity

Number

getPreorderSold(variantId)

Get number of preorders sold

Number

getPreorderTotal(variantId)

Get total preorder limit

Number

getLineItemProperties(sellingPlan, variantId)

Get properties for cart line items

Object


getSellingPlan


Get detailed preorder information for any variant. This gives you access to all the preorder configuration - button text, colors, shipping dates, limits, payment options, and more.


const sellingPlan = window._RestockRocket.getSellingPlan(variantId)


Returns: Object with selling plan data or null if no preorder is set up for that variant.


Basic Example:


const plan = window._RestockRocket.getSellingPlan("123456789");

if (plan) {
console.log("Preorder available:", plan.name);
console.log("Button text:", plan.preorder_button_text);
console.log("Shipping date:", plan.delivery_exact_time);
console.log("Payment options:", plan.payment_options);
} else {
console.log("No preorder available");
}


Key Selling Plan Properties:

{
// IDs & Basic Info
"shopify_selling_plan_group_id": 123456789,
"shopify_selling_plan_id": 987654321,
"enabled": true,
"variant_ids": [11111, 22222, 33333],
"name": "Preorder - Ships March 2024",

// Button Styling
"preorder_button_text": "Preorder Now",
"preorder_button_description": "Ships in 6 weeks",
"preorder_button_colors_enabled": true,
"preorder_button_background_color": "#000000",
"preorder_button_text_color": "#FFFFFF",

// Badge Config
"preorder_badge_enabled": true,
"preorder_badge_text": "Preorder",
"preorder_badge_background_color": "#000000",
"preorder_badge_text_color": "#FFFFFF",

// Shipping/Delivery
"delivery_type": "exact_time", // or "anchor", "asap", "unknown"
"delivery_exact_time": "2024-03-15",
"delivery_after_n_intervals": 30, // days
"preorder_shipping_text": "Ships {{ date }}",

// Payment & Pricing
"payment_options": [
{
"shopify_selling_plan_id": 111,
"billing_title": "Pay in Full",
"billing_description": "Full payment now",
"pricing_type": "percentage", // or "fixed_amount", "no_discount"
"pricing_percentage": 10
},
{
"shopify_selling_plan_id": 222,
"billing_title": "50% Deposit",
"billing_description": "Pay {{ payment }} now, {{ remaining }} later",
"billing_checkout_charge_type": "percentage",
"billing_checkout_charge_percentage": 50
}
],

// Inventory & Limits
"inventory_provider": "stoq", // or "shopify"
"preorder_progress_bar_enabled": true,

// Markets
"markets_enabled": false,
"shopify_market_ids": [1, 2, 3],

// Countdown Timer
"countdown_timer_enabled": true,
"countdown_timer_use_schedule_dates": true,
"schedule_start_date": "2024-03-01",
"schedule_end_date": "2024-03-31"
}


getPreorderAvailable / getPreorderSold / getPreorderTotal


Get real-time preorder inventory data to build custom availability displays.


const available = window._RestockRocket.getPreorderAvailable(variantId);
const sold = window._RestockRocket.getPreorderSold(variantId);
const total = window._RestockRocket.getPreorderTotal(variantId);


Example - Custom Availability Counter:


window.addEventListener('stoq:loaded', () => {
const variantId = '{{ product.selected_or_first_available_variant.id }}';
const plan = window._RestockRocket.getSellingPlan(variantId);

if (plan) {
const available = window._RestockRocket.getPreorderAvailable(variantId);
const sold = window._RestockRocket.getPreorderSold(variantId);
const total = window._RestockRocket.getPreorderTotal(variantId);

document.getElementById('preorder-stats').innerHTML = `
<div class="preorder-availability">
<span class="sold">${sold} preorders</span> /
<span class="total">${total} available</span>
</div>
<div class="remaining">${available} left at this price!</div>
`;
}
});




6. marketId


Returns the current Shopify market ID.


const marketId = window._RestockRocket.marketId()


Example:

const market = window._RestockRocket.marketId();
console.log("Current market:", market);

// Use for market-specific logic
if (market === "US") {
// Show US-specific messaging
}




Custom Events


STOQ dispatches custom events throughout the customer journey that you can tap into for analytics, custom UI updates, or integration with other tools. Every significant user action triggers an event with detailed context about what happened.


Event

Description

Detail Properties

stoq:loaded

App loaded

pageType, enabled, settings

stoq:modal:opened

Modal opened

productId, variantId, modalType

stoq:modal:closed

Modal closed

productId, variantId

stoq:intent:created

Customer signed up

intentId, variantId, channel, customerEmail

stoq:button:clicked

Button clicked

productId, variantId, buttonType

stoq:variant:changed

Variant changed

variantId, available, hasSellingPlan


Example - Track Signups with Analytics:


window.addEventListener('stoq:intent:created', (event) => {
const { intentId, variantId, channel } = event.detail;

// Track in Google Analytics
gtag('event', 'back_in_stock_signup', {
'event_category': 'engagement',
'event_label': `Variant ${variantId}`,
'channel': channel
});

console.log(`Customer signed up via ${channel}`);
});




Integration Examples


Quick Add Button on Collection Page


{% for product in collection.products %}
<div class="product-item">
<h2>{{ product.title }}</h2>
<button class="quick-add-btn"
data-product='{{ product | json | escape }}'
data-variant-id="{{ product.selected_or_first_available_variant.id }}">
Quick Add
</button>
</div>
{% endfor %}

<script>
window.addEventListener('stoq:loaded', () => {
document.querySelectorAll('.quick-add-btn').forEach(button => {
button.addEventListener('click', function() {
const productData = JSON.parse(this.getAttribute('data-product'));
const variantId = this.getAttribute('data-variant-id');

window._RestockRocket.openModal(productData, variantId);
});
});
});
</script>




Custom Variant Selector with Notify Button


  <select id="variant-selector">
{% for variant in product.variants %}
<option value="{{ variant.id }}"
{% if variant == current_variant %}selected{% endif %}>
{{ variant.title }}
</option>
{% endfor %}
</select>

<button id="add-to-cart-btn">Add to Cart</button>
<button id="notify-me-btn" style="display: none;">Notify Me</button>

<script>
const variantSelector = document.getElementById('variant-selector');
const addToCartBtn = document.getElementById('add-to-cart-btn');
const notifyMeBtn = document.getElementById('notify-me-btn');

function updateButtons(variantId) {
const variant = {{ product.variants | json }}
.find(v => v.id.toString() === variantId);

if (variant && variant.available) {
addToCartBtn.style.display = 'block';
notifyMeBtn.style.display = 'none';
} else {
addToCartBtn.style.display = 'none';
notifyMeBtn.style.display = 'block';
}
}

variantSelector.addEventListener('change', function() {
const selectedVariantId = this.value;
updateButtons(selectedVariantId);
window._RestockRocket.renderButtonForVariant(selectedVariantId);
});

notifyMeBtn.addEventListener('click', function() {
window._RestockRocket.openModal();
});

// Initial setup
updateButtons(variantSelector.value);
</script>




Custom Preorder Badge with Shipping Date


<div id="preorder-info"></div>

<script>
window.addEventListener('stoq:loaded', () => {
const variantId = '{{ product.selected_or_first_available_variant.id }}';
const plan = window._RestockRocket.getSellingPlan(variantId);

if (plan && plan.enabled) {
const badge = document.createElement('div');
badge.className = 'preorder-badge';
badge.style.backgroundColor = plan.preorder_badge_background_color;
badge.style.color = plan.preorder_badge_text_color;
badge.innerHTML = `
<span class="badge-text">${plan.preorder_badge_text}</span>
<span class="shipping-date">Ships: ${plan.delivery_exact_time}</span>
`;

document.getElementById('preorder-info').appendChild(badge);
}
});
</script>




Custom preorder flow with multiple payment options


Build a custom payment selector for preorder offers with multiple payment plans. This example shows how to access payment options and handle selection changes.


<div id="custom-preorder-container">
<div id="payment-options"></div>
<div id="preorder-details"></div>
<button id="add-to-cart">Add to Cart</button>
</div>

<script>
window.addEventListener('stoq:loaded', () => {
const variantId = '{{ product.selected_or_first_available_variant.id }}';
const plan = window._RestockRocket.getSellingPlan(variantId);

if (!plan || !plan.payment_options || plan.payment_options.length === 0) {
return;
}

const container = document.getElementById('payment-options');
let selectedPlanId = plan.payment_options[0].shopify_selling_plan_id;

// Render payment options
plan.payment_options.forEach((option, index) => {
const optionDiv = document.createElement('div');
optionDiv.className = 'payment-option';
optionDiv.innerHTML = `
<input type="radio"
name="payment"
id="payment-${index}"
value="${option.shopify_selling_plan_id}"
${index === 0 ? 'checked' : ''}>
<label for="payment-${index}">
<strong>${option.billing_title}</strong>
<span>${option.billing_description}</span>
${option.pricing_type !== 'no_discount' ?
`<span class="discount">${option.pricing_percentage}% off</span>` : ''}
</label>
`;

optionDiv.querySelector('input').addEventListener('change', (e) => {
selectedPlanId = e.target.value;
updatePreorderDetails(option);
});

container.appendChild(optionDiv);
});

// Update details display
function updatePreorderDetails(option) {
const details = document.getElementById('preorder-details');
const available = window._RestockRocket.getPreorderAvailable(variantId);

details.innerHTML = `
<p><strong>Shipping:</strong> ${plan.delivery_exact_time}</p>
<p><strong>Available:</strong> ${available} units</p>
<p><strong>Payment:</strong> ${option.billing_description}</p>
`;
}

// Initialize with first option
updatePreorderDetails(plan.payment_options[0]);

// Handle add to cart with selected plan
document.getElementById('add-to-cart').addEventListener('click', () => {
const properties = window._RestockRocket.getLineItemProperties(
plan,
variantId,
true // isPreorderItem
);

// Add to cart with properties
fetch('/cart/add.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: variantId,
quantity: 1,
selling_plan: selectedPlanId,
properties: properties
})
})
.then(response => response.json())
.then(data => {
console.log('Added to cart:', data);
window.location.href = '/cart';
});
});
});
</script>

<style>
.payment-option {
border: 2px solid #ddd;
padding: 15px;
margin: 10px 0;
border-radius: 8px;
cursor: pointer;
}
.payment-option input:checked + label {
font-weight: bold;
}
.discount {
background: #00a651;
color: white;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
</style>




Dynamic preorder progress bar


Show real-time preorder demand with a custom progress bar.


<div id="preorder-progress"></div>

<script>
window.addEventListener('stoq:loaded', () => {
const variantId = '{{ product.selected_or_first_available_variant.id }}';
const plan = window._RestockRocket.getSellingPlan(variantId);

if (plan) {
const sold = window._RestockRocket.getPreorderSold(variantId);
const total = window._RestockRocket.getPreorderTotal(variantId);
const available = window._RestockRocket.getPreorderAvailable(variantId);
const percentage = Math.round((sold / total) * 100);

document.getElementById('preorder-progress').innerHTML = `
<div class="progress-header">
<span>🔥 ${sold} preorders placed</span>
<span>${available} remaining</span>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: ${percentage}%"></div>
</div>
<p class="progress-label">${percentage}% claimed</p>
`;
}
});
</script>

<style>
.progress-bar {
width: 100%;
height: 20px;
background: #f0f0f0;
border-radius: 10px;
overflow: hidden;
margin: 10px 0;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #00a651, #00d563);
transition: width 0.3s ease;
}
.progress-header {
display: flex;
justify-content: space-between;
font-size: 14px;
margin-bottom: 5px;
}
</style>




Countdown timer for limited preorder


Create a custom countdown timer for time-sensitive preorder offers.


<div id="preorder-countdown"></div>

<script>
window.addEventListener('stoq:loaded', () => {
const variantId = '{{ product.selected_or_first_available_variant.id }}';
const plan = window._RestockRocket.getSellingPlan(variantId);

if (plan && plan.countdown_timer_enabled && plan.schedule_end_date) {
const endDate = new Date(plan.schedule_end_date);

function updateCountdown() {
const now = new Date();
const diff = endDate - now;

if (diff <= 0) {
document.getElementById('preorder-countdown').innerHTML =
'<p>Preorder has ended</p>';
return;
}

const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000);

document.getElementById('preorder-countdown').innerHTML = `
<div class="countdown">
<div class="countdown-unit">
<span class="value">${days}</span>
<span class="label">Days</span>
</div>
<div class="countdown-unit">
<span class="value">${hours}</span>
<span class="label">Hours</span>
</div>
<div class="countdown-unit">
<span class="value">${minutes}</span>
<span class="label">Mins</span>
</div>
<div class="countdown-unit">
<span class="value">${seconds}</span>
<span class="label">Secs</span>
</div>
</div>
<p class="countdown-text">Preorder ends soon!</p>
`;
}

updateCountdown();
setInterval(updateCountdown, 1000);
}
});
</script>

<style>
.countdown {
display: flex;
gap: 15px;
justify-content: center;
margin: 20px 0;
}
.countdown-unit {
text-align: center;
min-width: 60px;
}
.countdown-unit .value {
display: block;
font-size: 32px;
font-weight: bold;
background: #000;
color: #fff;
padding: 10px;
border-radius: 8px;
}
.countdown-unit .label {
display: block;
margin-top: 5px;
font-size: 12px;
text-transform: uppercase;
}
</style>





Full documentation


For complete API reference, detailed parameters, response schemas, and interactive testing:** docs.stoqapp.com/javascript-api & docs.stoqapp.com/custom-events. **If you need any assistance with custom integrations or using the JavaScript API, feel free to contact our 24/7 live support. We're happy to help!

Updated on: 14/01/2026

Was this article helpful?

Share your feedback

Cancel

Thank you!