@scriptive-au/shopify-ss-beacon
v1.1.0
Published
SearchSpring Beacon 2.0 tracking for Shopify themes — no build system required
Maintainers
Readme
@scriptive-au/shopify-ss-beacon
SearchSpring Beacon 2.0 tracking for Shopify themes. Works with or without a build system.
Install
CDN (no build system — Shopify themes)
<script src="https://cdn.jsdelivr.net/npm/@scriptive-au/shopify-ss-beacon@1/dist/beacon.min.js" defer></script>npm (stores with a build system)
npm install @scriptive-au/shopify-ss-beaconimport '@scriptive-au/shopify-ss-beacon';Setup
Set window.SSBeaconConfig before the script loads. In Shopify, do this in a Liquid snippet rendered at the top of <body>:
<script>
window.SSBeaconConfig = {
siteId: 'YOUR_SITE_ID',
initiator: 'your-store/custom/1.0.0',
shopperId: {{ customer.id | json }},
dev: false,
cart: {{ cart | json }}
};
</script>
<script src="https://cdn.jsdelivr.net/npm/@scriptive-au/shopify-ss-beacon@1/dist/beacon.min.js" defer></script>See example/searchspring-beacon.liquid for a complete drop-in snippet.
Config options
| Key | Type | Required | Description |
|-----|------|----------|-------------|
| siteId | string | yes | SearchSpring site ID |
| initiator | string | no | Integration identifier (default: custom/1.0.0) |
| shopperId | string | no | Logged-in customer ID or email — sent with every event when set |
| dev | boolean | no | Sandbox mode — events are tagged as test data (default: false) |
| cart | object | no | Current Shopify cart object ({{ cart \| json }}). Seeds cart diff tracking |
Usage
The observe*Grid methods wire up impression and click tracking on a grid container. Impressions use IntersectionObserver (70 % visible, 1 s dwell), clicks use a delegated handler. Cards added later (infinite scroll, etc.) are picked up automatically.
Each observe*Grid call returns a disconnect function. Call it before re-observing (e.g. after pagination or filtering) to tear down old observers and listeners:
var disconnect = SSBeacon.observeSearchGrid(responseId, gridEl);
// Later, when results change:
disconnect();
disconnect = SSBeacon.observeSearchGrid(newResponseId, gridEl);Requirements on your product cards (for observe*Grid only):
- Each card needs a
data-ss-uidattribute (the product uid from SS) - Optionally
data-ss-parent-id(defaults to uid if omitted)
<div class="product-item" data-ss-uid="abc123" data-ss-parent-id="abc123">
<a href="/products/my-product">...</a>
</div>If observe*Grid doesn't suit your setup (custom carousels, virtual lists, etc.), use the manual *Impression / *Click methods instead. Same item shape, fires immediately:
// Manual impression (e.g. from your own visibility callback):
SSBeacon.searchImpression(responseId, [
{ type: 'product', parentId: '123', uid: '456' }
]);
// Manual click:
SSBeacon.searchClick(responseId, { type: 'product', parentId: '123', uid: '456' });Search
// After rendering search results:
var disconnect = SSBeacon.observeSearchGrid(responseId, gridElement);
SSBeacon.searchRender(responseId);
// If SS API returns a redirect instead of results:
SSBeacon.searchRedirect(responseId, redirectUrl);
// Quick add to cart from search grid:
SSBeacon.searchAddToCart(responseId, [
{ parentId: '123', uid: '456', qty: 1, price: 29.99 }
]);Category
SSBeacon.categoryRender(responseId);
var disconnect = SSBeacon.observeCategoryGrid(responseId, gridElement);
// Quick add to cart from category grid:
SSBeacon.categoryAddToCart(responseId, [
{ parentId: '123', uid: '456', qty: 1, price: 29.99 }
]);Autocomplete
// When autocomplete dropdown renders:
SSBeacon.autocompleteRender(responseId);
var disconnect = SSBeacon.observeAutocompleteGrid(responseId, dropdownElement);
// If SS API returns a redirect:
SSBeacon.autocompleteRedirect(responseId, redirectUrl);
// Quick add from autocomplete dropdown:
SSBeacon.autocompleteAddToCart(responseId, [
{ parentId: '123', uid: '456', qty: 1, price: 29.99 }
]);Recommendations
All recommendation events take a tag that identifies the profile (e.g. "similar", "trending", "recently-viewed").
SSBeacon.recommendationsRender(tag, responseId);
var disconnect = SSBeacon.observeRecommendationsGrid(tag, responseId, gridElement);
// Quick add from recommendations:
SSBeacon.recommendationsAddToCart(tag, responseId, [
{ parentId: '123', uid: '456', qty: 1, price: 49.99 }
]);Cart
After any cart change, pass the fresh Shopify cart object. The beacon diffs it against the last known state to detect adds and removes:
fetch('/cart.js')
.then(r => r.json())
.then(data => {
if (window.SSBeacon) SSBeacon.cartUpdate(data);
});Note: Shopper login, product pageview, and order transaction events are handled by the SearchSpring Shopify Pixel app — don't duplicate them here.
API Reference
Search (docs)
| Method | Beacon endpoint | Notes |
|--------|-----------------|-------|
| searchRender(responseId) | search/render | |
| searchRedirect(responseId, url) | search/redirect | |
| searchImpression(responseId, items) | search/impression | Manual |
| searchClick(responseId, items) | search/clickthrough | Manual |
| observeSearchGrid(responseId, gridEl) | search/impression + search/clickthrough | Returns disconnect fn |
| searchAddToCart(responseId, items) | search/addtocart | |
Category (docs)
| Method | Beacon endpoint | Notes |
|--------|-----------------|-------|
| categoryRender(responseId) | category/render | |
| categoryImpression(responseId, items) | category/impression | Manual |
| categoryClick(responseId, items) | category/clickthrough | Manual |
| observeCategoryGrid(responseId, gridEl) | category/impression + category/clickthrough | Returns disconnect fn |
| categoryAddToCart(responseId, items) | category/addtocart | |
Autocomplete (docs)
| Method | Beacon endpoint | Notes |
|--------|-----------------|-------|
| autocompleteRender(responseId) | autocomplete/render | |
| autocompleteRedirect(responseId, url) | autocomplete/redirect | |
| autocompleteImpression(responseId, items) | autocomplete/impression | Manual |
| autocompleteClick(responseId, items) | autocomplete/clickthrough | Manual |
| observeAutocompleteGrid(responseId, gridEl) | autocomplete/impression + autocomplete/clickthrough | Returns disconnect fn |
| autocompleteAddToCart(responseId, items) | autocomplete/addtocart | |
Recommendations (docs)
| Method | Beacon endpoint | Notes |
|--------|-----------------|-------|
| recommendationsRender(tag, responseId) | recommendations/render | |
| recommendationsImpression(tag, responseId, items) | recommendations/impression | Manual |
| recommendationsClick(tag, responseId, items) | recommendations/clickthrough | Manual |
| observeRecommendationsGrid(tag, responseId, gridEl) | recommendations/impression + recommendations/clickthrough | Returns disconnect fn |
| recommendationsAddToCart(tag, responseId, items) | recommendations/addtocart | |
Cart (docs)
| Method | Beacon endpoint |
|--------|-----------------|
| cartUpdate(shopifyCart) | cart/add + cart/remove |
How it works
- Non-blocking: Beacons fire via
requestIdleCallback→navigator.sendBeacon(text/plainto skip CORS preflight) - Impressions: Per-card IntersectionObserver (70 % visible, 1 s dwell). Batched every 500 ms. Deduped per module + tag. MutationObserver catches late-added cards
- Clicks: Delegated click handler on the grid — works for static and dynamic cards
- Cart: Keeps an internal cart snapshot and diffs on each
cartUpdate()to find adds/removes - Session: Auto-manages
ss-user-id(2 yr cookie) andss-session-id(30 min sliding)
Versioning
Uses semver. The CDN URL @1 resolves to the latest 1.x release. Pin to @1.0.0 for an exact version.
Dev
npm install
npm run build # → dist/beacon.js + dist/beacon.min.jsLicense
MIT
