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 forcustomerData(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
openModalplus: inlineFormContainer(String, optional): CSS selector for containerinlineFormContainerInsertType(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 varianttargetElement(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 |
|---|---|---|
| Get preorder config for a variant | Object or null |
| Get available preorder quantity | Number |
| Get number of preorders sold | Number |
| Get total preorder limit | Number |
| 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 |
|---|---|---|
| App loaded | pageType, enabled, settings |
| Modal opened | productId, variantId, modalType |
| Modal closed | productId, variantId |
| Customer signed up | intentId, variantId, channel, customerEmail |
| Button clicked | productId, variantId, buttonType |
| 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
Thank you!