Build a price and stock watcher with MutationObserver
You want that item, but only at the right price — or you want to know the moment a sold-out product is back. Refreshing the tab all day is no way to live. This article builds a price and stock watcher with a JustZix JS rule: a MutationObserver watches the relevant element, compares the value, and alerts you when your condition is met.
How the watcher works
The plan has four parts, and each maps to a section below:
- Pick a stable selector for the price or stock element.
- Attach a
MutationObserverso you react to changes without polling. - Parse the text into a comparable number or boolean.
- Notify yourself — an on-page banner or a system notification — and remember the last value.
It all runs client-side in the tab. Keep the product page open (a pinned tab is ideal) and the rule does the watching.
Picking a stable selector
The watcher is only as reliable as its selector. Open DevTools, Elements panel, and find the element holding the price. Prefer, in this order:
- A semantic hook:
[itemprop="price"],[data-price],[data-testid="product-price"]. These rarely change. - A stable ID:
#priceblock_ourprice,#product-price. - A meaningful class:
.price-now,.product__price.
Avoid hashed classes (.css-1a2b3c) and long descendant chains — they break on the next deploy. Many shops also embed structured data in a <script type="application/ld+json"> block; if the visible DOM is messy, parsing that JSON is often the most stable route.
Setting up the MutationObserver
A MutationObserver fires a callback whenever the watched subtree changes — no polling, no wasted CPU. Watch the price node for text and child changes:
const SELECTOR = '[itemprop="price"]'; // your stable selector
const priceEl = document.querySelector(SELECTOR);
if (!priceEl) {
console.warn('[watcher] price element not found - check the selector');
} else {
const observer = new MutationObserver(() => checkPrice(priceEl));
observer.observe(priceEl, {
childList: true,
subtree: true,
characterData: true
});
// Also check once on load - the price may already be a deal
checkPrice(priceEl);
}
Single-page-app shops sometimes replace the whole price node on update. If your observer goes silent, observe a stable parent container with subtree: true and re-query the price inside the callback.
Parsing the price into a number
Price text is messy: currency symbols, thousands separators, decimal commas. Strip it down to a clean number before comparing:
// Turn "$1,299.00" or "1 299,00 zl" into 1299
function parsePrice(text) {
if (!text) return NaN;
// Keep digits, dot and comma; drop everything else
let s = text.replace(/[^0-9.,]/g, '');
// If comma is the decimal separator, normalise it
if (/,\d{2}$/.test(s)) s = s.replace(/\./g, '').replace(',', '.');
else s = s.replace(/,/g, '');
return parseFloat(s);
}
Comparing against a threshold
Now the decision logic. Compare the parsed price to your target and only alert when it actually crosses — and only once per drop, so you are not spammed on every minor DOM mutation:
const TARGET = 999; // alert when price drops to/below this
const STORAGE_KEY = 'jz-watch-' + location.pathname;
function checkPrice(el) {
const price = parsePrice(el.textContent);
if (Number.isNaN(price)) return;
const last = parseFloat(localStorage.getItem(STORAGE_KEY)) || Infinity;
localStorage.setItem(STORAGE_KEY, String(price));
console.log('[watcher] price now', price, '- last', last);
// Fire only when we newly cross the threshold
if (price <= TARGET && last > TARGET) {
notify('Price drop! Now ' + price + ' (target ' + TARGET + ')');
}
}
Watching for back-in-stock
Same pattern, different signal. Instead of a number you watch a boolean — is the buy button enabled, is the "out of stock" label gone:
function checkStock() {
const soldOut = document.querySelector('[class*="out-of-stock" i], .sold-out');
const buyBtn = document.querySelector('button[name="add-to-cart"], .add-to-cart');
const inStock = !soldOut && buyBtn && !buyBtn.disabled;
const wasInStock = localStorage.getItem('jz-stock') === 'yes';
localStorage.setItem('jz-stock', inStock ? 'yes' : 'no');
if (inStock && !wasInStock) {
notify('Back in stock! Grab it now.');
}
}
Notifying yourself
An alert is useless if you do not see it. Use two channels: an unmissable on-page banner, plus a system notification for when the tab is in the background.
function notify(message) {
// 1. On-page banner - always visible in the tab
let bar = document.getElementById('jz-watch-bar');
if (!bar) {
bar = document.createElement('div');
bar.id = 'jz-watch-bar';
bar.style.cssText =
'position:fixed;top:0;left:0;right:0;z-index:2147483647;' +
'background:#16a34a;color:#fff;font:600 15px/1.4 sans-serif;' +
'padding:12px 16px;text-align:center;';
document.body.appendChild(bar);
}
bar.textContent = 'JZ watcher: ' + message;
// 2. System notification - works when the tab is hidden
if (Notification.permission === 'granted') {
new Notification('JustZix watcher', { body: message });
} else if (Notification.permission !== 'denied') {
Notification.requestPermission();
}
}
The z-index of 2147483647 is the maximum 32-bit integer — it guarantees the banner sits above anything the site renders. System notifications need permission, which the browser only grants from a user gesture or a previously approved origin, so the first run may just request it; the banner covers you in the meantime.
Persisting the last seen value
You saw localStorage used above, and it does real work. It survives reloads, so the watcher knows whether a change is genuinely new. It also de-duplicates alerts — you compare against the stored value and only notify on a real crossing. Key it per page (location.pathname) so several product watchers can coexist without overwriting each other.
Wiring it up in JustZix
- Create a rule with the URL pattern of the exact product, e.g.
https://shop.example.com/product/12345*. - Paste the combined script into the JS tab — set
SELECTORandTARGETfor that product. - Pin the tab and leave it open; the observer reacts whenever the shop updates the DOM.
- Want a periodic re-check even without DOM changes? Add a
setIntervalthat reloads the page every few minutes.
See also
- Disable dark patterns — strip fake urgency so you see the real price.
- Mock API responses — test your watcher against a fake price feed.
- JustZix use cases for more automation ideas.
Stop refreshing that tab. Install JustZix, drop the watcher script into a per-product rule, and let the browser tell you the moment the price is right.
Rate this post
No ratings yet — be the first.