JAVASCRIPTshopifyintermediate
Shopify Back in Stock Notifications
Let customers subscribe to email notifications when out-of-stock products are available
Faisal Yaqoob
November 23, 2025
#shopify#inventory#notifications#email#stock-alerts
Code
javascript
1 // Back in Stock Notification Class 2 class BackInStockNotifier { 3 constructor(options = {}) { 4 this.productId = options.productId; 5 this.variantId = options.variantId; 6 this.button = document.querySelector(options.button || '[data-notify-button]'); 7 this.modal = document.querySelector(options.modal || '[data-notify-modal]'); 8 this.form = document.querySelector(options.form || '[data-notify-form]'); 9
10 if (!this.button) return; 11
12 this.init(); 13 } 14
15 init() { 16 // Show modal on button click 17 this.button.addEventListener('click', () => this.showModal()); 18
19 // Handle form submission 20 if (this.form) { 21 this.form.addEventListener('submit', (e) => this.handleSubmit(e)); 22 } 23
24 // Close modal 25 const closeBtn = this.modal?.querySelector('[data-close-modal]'); 26 if (closeBtn) { 27 closeBtn.addEventListener('click', () => this.hideModal()); 28 } 29
30 // Close on overlay click 31 this.modal?.addEventListener('click', (e) => { 32 if (e.target === this.modal) { 33 this.hideModal(); 34 } 35 }); 36 } 37
38 showModal() { 39 if (!this.modal) { 40 this.createModal(); 41 } 42
43 this.modal.classList.add('is-active'); 44 document.body.style.overflow = 'hidden'; 45 } 46
47 hideModal() { 48 this.modal.classList.remove('is-active'); 49 document.body.style.overflow = ''; 50 } 51
52 createModal() { 53 this.modal = document.createElement('div'); 54 this.modal.className = 'notify-modal'; 55 this.modal.setAttribute('data-notify-modal', ''); 56
57 this.modal.innerHTML = ` 58 <div class="notify-modal-content"> 59 <button class="notify-close" data-close-modal>×</button> 60
61 <h3>Notify Me When Available</h3> 62 <p>Enter your email and we'll notify you when this product is back in stock.</p> 63
64 <form data-notify-form class="notify-form"> 65 <input 66 type="email" 67 name="email" 68 placeholder="your@email.com" 69 required 70 class="notify-input"> 71
72 <input type="hidden" name="product_id" value="${this.productId}"> 73 <input type="hidden" name="variant_id" value="${this.variantId}"> 74
75 <button type="submit" class="btn btn--notify"> 76 Notify Me 77 </button> 78 </form> 79
80 <div class="notify-success" style="display: none;"> 81 <p>✓ You'll be notified when this item is back in stock!</p> 82 </div> 83
84 <p class="notify-privacy"> 85 We respect your privacy. Unsubscribe at any time. 86 </p> 87 </div> 88 `; 89
90 document.body.appendChild(this.modal); 91
92 this.form = this.modal.querySelector('[data-notify-form]'); 93 this.form.addEventListener('submit', (e) => this.handleSubmit(e)); 94
95 this.modal.addEventListener('click', (e) => { 96 if (e.target === this.modal) { 97 this.hideModal(); 98 } 99 }); 100 } 101
102 async handleSubmit(e) { 103 e.preventDefault(); 104
105 const formData = new FormData(this.form); 106 const email = formData.get('email'); 107 const productId = formData.get('product_id'); 108 const variantId = formData.get('variant_id'); 109
110 try { 111 // Store notification request (requires backend/app) 112 const response = await this.saveNotificationRequest(email, productId, variantId); 113
114 if (response.ok) { 115 this.showSuccess(); 116 } else { 117 this.showError(); 118 } 119 } catch (error) { 120 console.error('Notification signup error:', error); 121 this.showError(); 122 } 123 } 124
125 async saveNotificationRequest(email, productId, variantId) { 126 // Option 1: Use a Shopify app endpoint 127 // Most back-in-stock apps provide an API endpoint 128
129 // Option 2: Save to your own backend 130 return await fetch('/apps/back-in-stock/subscribe', { 131 method: 'POST', 132 headers: { 133 'Content-Type': 'application/json', 134 }, 135 body: JSON.stringify({ 136 email, 137 product_id: productId, 138 variant_id: variantId, 139 shop: window.Shopify.shop 140 }) 141 }); 142
143 // Option 3: Use customer metafields (Shopify Plus) 144 // Store in customer metafield 145
146 // Option 4: Email to store owner (simple fallback) 147 /* 148 return await fetch('/contact', { 149 method: 'POST', 150 headers: { 151 'Content-Type': 'application/json', 152 }, 153 body: JSON.stringify({ 154 contact: { 155 email: email, 156 body: `Back in stock notification request for product ${productId}, variant ${variantId}` 157 } 158 }) 159 }); 160 */ 161 } 162
163 showSuccess() { 164 this.form.style.display = 'none'; 165 const success = this.modal.querySelector('.notify-success'); 166 if (success) { 167 success.style.display = 'block'; 168 } 169
170 // Auto-close after 3 seconds 171 setTimeout(() => { 172 this.hideModal(); 173 this.resetForm(); 174 }, 3000); 175 } 176
177 showError() { 178 alert('Something went wrong. Please try again.'); 179 } 180
181 resetForm() { 182 this.form.reset(); 183 this.form.style.display = 'block'; 184 const success = this.modal.querySelector('.notify-success'); 185 if (success) { 186 success.style.display = 'none'; 187 } 188 } 189 } 190
191 // Auto-initialize for sold out products 192 document.addEventListener('DOMContentLoaded', () => { 193 // Check if product is sold out 194 const addToCartBtn = document.querySelector('[data-add-to-cart]'); 195 const notifyBtn = document.querySelector('[data-notify-button]'); 196
197 if (addToCartBtn && notifyBtn) { 198 const isSoldOut = addToCartBtn.disabled || addToCartBtn.textContent.includes('Sold Out'); 199
200 if (isSoldOut) { 201 addToCartBtn.style.display = 'none'; 202 notifyBtn.style.display = 'block'; 203
204 const productId = notifyBtn.dataset.productId; 205 const variantId = notifyBtn.dataset.variantId; 206
207 new BackInStockNotifier({ 208 productId, 209 variantId 210 }); 211 } 212 } 213 });
Shopify Back in Stock Notifications
Allow customers to sign up for email notifications when out-of-stock products become available again, helping you capture lost sales and build your email list.
// Back in Stock Notification Class
class BackInStockNotifier {
constructor(options = {}) {
this.productId = options.productId;
this.variantId = options.variantId;
this.button = document.querySelector(options.button || '[data-notify-button]');
this.modal = document.querySelector(options.modal || '[data-notify-modal]');
this.form = document.querySelector(options.form || '[data-notify-form]');
if (!this.button) return;
this.init();
}
init() {
// Show modal on button click
this.button.addEventListener('click', () => this.showModal());
// Handle form submission
if (this.form) {
this.form.addEventListener('submit', (e) => this.handleSubmit(e));
}
// Close modal
const closeBtn = this.modal?.querySelector('[data-close-modal]');
if (closeBtn) {
closeBtn.addEventListener('click', () => this.hideModal());
}
// Close on overlay click
this.modal?.addEventListener('click', (e) => {
if (e.target === this.modal) {
this.hideModal();
}
});
}
showModal() {
if (!this.modal) {
this.createModal();
}
this.modal.classList.add('is-active');
document.body.style.overflow = 'hidden';
}
hideModal() {
this.modal.classList.remove('is-active');
document.body.style.overflow = '';
}
createModal() {
this.modal = document.createElement('div');
this.modal.className = 'notify-modal';
this.modal.setAttribute('data-notify-modal', '');
this.modal.innerHTML = `
<div class="notify-modal-content">
<button class="notify-close" data-close-modal>×</button>
<h3>Notify Me When Available</h3>
<p>Enter your email and we'll notify you when this product is back in stock.</p>
<form data-notify-form class="notify-form">
<input
type="email"
name="email"
placeholder="your@email.com"
required
class="notify-input">
<input type="hidden" name="product_id" value="${this.productId}">
<input type="hidden" name="variant_id" value="${this.variantId}">
<button type="submit" class="btn btn--notify">
Notify Me
</button>
</form>
<div class="notify-success" style="display: none;">
<p>✓ You'll be notified when this item is back in stock!</p>
</div>
<p class="notify-privacy">
We respect your privacy. Unsubscribe at any time.
</p>
</div>
`;
document.body.appendChild(this.modal);
this.form = this.modal.querySelector('[data-notify-form]');
this.form.addEventListener('submit', (e) => this.handleSubmit(e));
this.modal.addEventListener('click', (e) => {
if (e.target === this.modal) {
this.hideModal();
}
});
}
async handleSubmit(e) {
e.preventDefault();
const formData = new FormData(this.form);
const email = formData.get('email');
const productId = formData.get('product_id');
const variantId = formData.get('variant_id');
try {
// Store notification request (requires backend/app)
const response = await this.saveNotificationRequest(email, productId, variantId);
if (response.ok) {
this.showSuccess();
} else {
this.showError();
}
} catch (error) {
console.error('Notification signup error:', error);
this.showError();
}
}
async saveNotificationRequest(email, productId, variantId) {
// Option 1: Use a Shopify app endpoint
// Most back-in-stock apps provide an API endpoint
// Option 2: Save to your own backend
return await fetch('/apps/back-in-stock/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email,
product_id: productId,
variant_id: variantId,
shop: window.Shopify.shop
})
});
// Option 3: Use customer metafields (Shopify Plus)
// Store in customer metafield
// Option 4: Email to store owner (simple fallback)
/*
return await fetch('/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
contact: {
email: email,
body: `Back in stock notification request for product ${productId}, variant ${variantId}`
}
})
});
*/
}
showSuccess() {
this.form.style.display = 'none';
const success = this.modal.querySelector('.notify-success');
if (success) {
success.style.display = 'block';
}
// Auto-close after 3 seconds
setTimeout(() => {
this.hideModal();
this.resetForm();
}, 3000);
}
showError() {
alert('Something went wrong. Please try again.');
}
resetForm() {
this.form.reset();
this.form.style.display = 'block';
const success = this.modal.querySelector('.notify-success');
if (success) {
success.style.display = 'none';
}
}
}
// Auto-initialize for sold out products
document.addEventListener('DOMContentLoaded', () => {
// Check if product is sold out
const addToCartBtn = document.querySelector('[data-add-to-cart]');
const notifyBtn = document.querySelector('[data-notify-button]');
if (addToCartBtn && notifyBtn) {
const isSoldOut = addToCartBtn.disabled || addToCartBtn.textContent.includes('Sold Out');
if (isSoldOut) {
addToCartBtn.style.display = 'none';
notifyBtn.style.display = 'block';
const productId = notifyBtn.dataset.productId;
const variantId = notifyBtn.dataset.variantId;
new BackInStockNotifier({
productId,
variantId
});
}
}
});
Liquid Template
<!-- Product Form with Notify Button -->
<div class="product-actions">
{% if product.available %}
<button
type="submit"
name="add"
data-add-to-cart
class="btn btn--add-to-cart">
Add to Cart
</button>
{% else %}
<button
type="button"
data-notify-button
data-product-id="{{ product.id }}"
data-variant-id="{{ product.selected_or_first_available_variant.id }}"
class="btn btn--notify"
style="display: none;">
Notify Me When Available
</button>
{% endif %}
</div>
<!-- Variant change handling -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const variantSelect = document.querySelector('[data-variant-select]');
const addToCartBtn = document.querySelector('[data-add-to-cart]');
const notifyBtn = document.querySelector('[data-notify-button]');
if (variantSelect && addToCartBtn && notifyBtn) {
variantSelect.addEventListener('change', (e) => {
const selectedOption = e.target.options[e.target.selectedIndex];
const available = selectedOption.dataset.available === 'true';
const variantId = selectedOption.value;
if (available) {
addToCartBtn.style.display = 'block';
addToCartBtn.disabled = false;
notifyBtn.style.display = 'none';
} else {
addToCartBtn.style.display = 'none';
notifyBtn.style.display = 'block';
notifyBtn.dataset.variantId = variantId;
}
});
}
});
</script>
CSS Styling
/* Notify Button */
.btn--notify {
width: 100%;
padding: 15px 30px;
background: #0066cc;
color: #fff;
border: none;
border-radius: 4px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: background 0.3s;
}
.btn--notify:hover {
background: #0052a3;
}
/* Modal */
.notify-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.notify-modal.is-active {
opacity: 1;
visibility: visible;
}
.notify-modal-content {
background: #fff;
border-radius: 8px;
padding: 40px;
max-width: 500px;
width: 90%;
position: relative;
transform: scale(0.9);
transition: transform 0.3s ease;
}
.notify-modal.is-active .notify-modal-content {
transform: scale(1);
}
.notify-close {
position: absolute;
top: 15px;
right: 15px;
background: none;
border: none;
font-size: 30px;
cursor: pointer;
line-height: 1;
padding: 5px;
}
.notify-modal-content h3 {
margin-bottom: 10px;
font-size: 24px;
}
.notify-modal-content > p {
margin-bottom: 25px;
color: #666;
}
/* Form */
.notify-form {
margin-bottom: 20px;
}
.notify-input {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
margin-bottom: 15px;
}
.notify-input:focus {
outline: none;
border-color: #0066cc;
}
.btn--notify {
width: 100%;
}
/* Success Message */
.notify-success {
padding: 20px;
background: #d4edda;
border: 1px solid #c3e6cb;
border-radius: 4px;
color: #155724;
text-align: center;
margin-bottom: 20px;
}
/* Privacy Note */
.notify-privacy {
font-size: 12px;
color: #999;
text-align: center;
margin: 0;
}
Integration with Klaviyo
// Use Klaviyo for back-in-stock emails
async saveNotificationRequest(email, productId, variantId) {
const klaviyoListId = 'YOUR_KLAVIYO_LIST_ID';
return await fetch('https://a.klaviyo.com/api/v2/list/' + klaviyoListId + '/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
api_key: 'YOUR_KLAVIYO_PUBLIC_API_KEY',
profiles: [{
email: email,
$consent: 'email',
properties: {
notify_product_id: productId,
notify_variant_id: variantId,
}
}]
})
});
}
Server-Side Notification Handler (Example)
// Example backend endpoint to handle notifications
// This would run on your server or Shopify app
async function sendBackInStockEmail(productId, variantId) {
// Get all subscribers for this product/variant
const subscribers = await getSubscribers(productId, variantId);
// Get product details
const product = await shopify.product.get(productId);
const variant = product.variants.find(v => v.id == variantId);
// Send emails
for (const subscriber of subscribers) {
await sendEmail({
to: subscriber.email,
subject: `${product.title} is back in stock!`,
template: 'back-in-stock',
data: {
product_title: product.title,
product_url: `https://yourstore.com/products/${product.handle}`,
variant_title: variant.title,
product_image: product.images[0]?.src
}
});
// Mark as notified
await markAsNotified(subscriber.id);
}
}
Features
- Email Capture: Build email list from out-of-stock products
- Variant Support: Track specific variants
- Modal Interface: Clean, professional popup
- Privacy Focused: Clear privacy messaging
- Auto-Detection: Shows automatically for sold-out items
- Success Feedback: Visual confirmation of subscription
- Multiple Integrations: Works with Klaviyo, custom backends, apps
- Responsive: Mobile-friendly design
Related Snippets
Shopify Wishlist with LocalStorage
Add wishlist/favorites functionality without requiring a Shopify app
JAVASCRIPTshopifyintermediate
javascriptPreview
// Wishlist Class
class Wishlist {
constructor(options = {}) {
this.storageKey = options.storageKey || 'shopify_wishlist';
...#shopify#wishlist#favorites+1
11/28/2025
View
Shopify AJAX Collection Filters
Add dynamic filtering to collection pages without page reloads
JAVASCRIPTshopifyadvanced
javascriptPreview
// Collection Filters Class
class CollectionFilters {
constructor(options = {}) {
this.container = document.querySelector(options.container || '[data-collection-container]');
...#shopify#filters#ajax+2
11/27/2025
View
Shopify Recently Viewed Products
Track and display products customers have recently viewed
JAVASCRIPTshopifybeginner
javascriptPreview
// Recently Viewed Products Class
class RecentlyViewed {
constructor(options = {}) {
this.storageKey = options.storageKey || 'recently_viewed_products';
...#shopify#recently-viewed#localstorage+1
11/26/2025
View