Wishlist Page
Build a dedicated wishlist page with product grid, remove, and add-to-cart using the Swym JS SDK.
Wishlist Page
A dedicated page showing all wishlisted products with remove and add-to-cart actions.
Setup
Step 1 — Create a page template
In your theme code editor (Online Store > Themes > Edit code):
- OS 2.0 (JSON templates): Create
templates/page.wishlist.jsonthat references a new section:
{
"sections": {
"main": {
"type": "swym-wishlist-page",
"settings": {}
}
},
"order": ["main"]
}Then create sections/swym-wishlist-page.liquid with the Liquid below.
- Liquid templates: Create
templates/page.wishlist.liquiddirectly with the Liquid below.
Step 2 — Create a Shopify page
In your Shopify admin: Online Store > Pages > Add page. Set the title to "Wishlist". In the Theme template dropdown (right sidebar), select page.wishlist.
Step 3 — Add the JavaScript asset
Create assets/swym-wishlist-page.js in your theme with the JavaScript below.
Section Liquid — sections/swym-wishlist-page.liquid
sections/swym-wishlist-page.liquid{% comment %} Swym Wishlist Page — SDK-Only Mode {% endcomment %}
<div id="swym-wishlist-page" class="page-width">
<h1>My Wishlist <span id="swym-wishlist-count"></span></h1>
{%- comment -%} Login nudge for anonymous users {%- endcomment -%}
<div id="swym-login-nudge" style="display:none;">
<p>Sign in to save your wishlist across devices</p>
<a href="/account/login?return_url=/pages/wishlist">Sign in</a>
</div>
{%- comment -%} Save nudge — shown after 3+ items for anonymous users {%- endcomment -%}
<div id="swym-save-nudge" style="display:none;">
<p><strong>Don't lose your list!</strong> You've saved <span id="swym-save-nudge-count"></span> items. <a href="/account/login?return_url=/pages/wishlist">Sign in to keep them.</a></p>
<button id="swym-save-nudge-dismiss" aria-label="Dismiss">×</button>
</div>
<div id="swym-wishlist-loading">Loading your wishlist...</div>
<div id="swym-wishlist-empty" style="display:none;">
<p>Your wishlist is empty</p>
<p>Start adding items you love</p>
<a href="/collections">Browse products</a>
</div>
<div id="swym-wishlist-grid"></div>
</div>
<script src="{{ 'swym-wishlist-page.js' | asset_url }}" defer></script>Adapting Cards to Your Theme
The wishlist page product cards must match your collection page's card layout. A shopper should not be able to tell the wishlist page was built separately.
Before writing the JavaScript, read your collection section file and identify:
- Image wrapper — aspect ratio,
object-fit, CSS classes - Title element — HTML tag, class, font styling
- Price element — format, class, currency symbol
- Card wrapper — classes, spacing
- Grid —
grid-template-columns,gap, responsive breakpoints
Then replicate that structure in the renderProducts function below. The default implementation uses generic .swym-wishlist-card classes — replace these with your theme's actual card classes.
JavaScript — assets/swym-wishlist-page.js
assets/swym-wishlist-page.js(function() {
'use strict';
var SAVE_NUDGE_THRESHOLD = 3;
function initWishlistPage(swat) {
// Use the SDK's built-in login check — this is the canonical way.
// Under the hood, Swym's Liquid snippet sets window.swymCustomerId
// from Shopify's {{ customer.id }} object. The SDK reads it on init.
var isLoggedIn = swat.platform.isLoggedIn();
var grid = document.getElementById('swym-wishlist-grid');
var loading = document.getElementById('swym-wishlist-loading');
var empty = document.getElementById('swym-wishlist-empty');
var countEl = document.getElementById('swym-wishlist-count');
if (!grid) return;
// Show login nudge for anonymous users
if (!isLoggedIn) {
var loginNudge = document.getElementById('swym-login-nudge');
if (loginNudge) loginNudge.style.display = '';
}
swat.fetch(function(products) {
if (loading) loading.style.display = 'none';
if (!products || products.length === 0) {
if (empty) empty.style.display = 'block';
if (countEl) countEl.textContent = '(0 items)';
return;
}
if (countEl) countEl.textContent = '(' + products.length + ' items)';
showSaveNudge(swat, products.length);
renderProducts(swat, products, grid, countEl, empty);
});
// Re-render on wishlist changes
if (swat.evtLayer) {
swat.evtLayer.addEventListener('sw:removedfromwishlist', function() {
swat.fetch(function(products) {
renderProducts(swat, products, grid, countEl, empty);
if (countEl) countEl.textContent = '(' + (products ? products.length : 0) + ' items)';
});
});
}
}
function showSaveNudge(swat, count) {
if (swat.platform.isLoggedIn()) return;
if (count < SAVE_NUDGE_THRESHOLD) return;
if (sessionStorage.getItem('swym-save-nudge-dismissed')) return;
var nudge = document.getElementById('swym-save-nudge');
if (!nudge) return;
var countSpan = document.getElementById('swym-save-nudge-count');
if (countSpan) countSpan.textContent = count;
nudge.style.display = '';
var dismiss = document.getElementById('swym-save-nudge-dismiss');
if (dismiss) {
dismiss.addEventListener('click', function() {
nudge.style.display = 'none';
sessionStorage.setItem('swym-save-nudge-dismissed', 'true');
});
}
}
function renderProducts(swat, products, container, countEl, emptyEl) {
container.innerHTML = '';
if (!products || products.length === 0) {
if (emptyEl) emptyEl.style.display = 'block';
return;
}
if (emptyEl) emptyEl.style.display = 'none';
// IMPORTANT: Replace the HTML below with your theme's collection card structure
// to match the look and feel of your collection page
products.forEach(function(product) {
var card = document.createElement('div');
card.className = 'swym-wishlist-card';
// Product image
var imgLink = document.createElement('a');
imgLink.href = product.du || '#';
var img = document.createElement('img');
img.src = product.iu || '';
img.alt = product.dt || 'Product';
img.loading = 'lazy';
imgLink.appendChild(img);
card.appendChild(imgLink);
// Product title
var titleLink = document.createElement('a');
titleLink.href = product.du || '#';
titleLink.textContent = product.dt || 'Product';
card.appendChild(titleLink);
// Price
if (product.pr) {
var priceEl = document.createElement('span');
priceEl.textContent = formatPrice(product.pr);
card.appendChild(priceEl);
}
// Actions container
var actions = document.createElement('div');
actions.className = 'swym-wishlist-card-actions';
// Add to Cart
var inStock = product.stk !== 0;
var cartBtn = document.createElement('button');
cartBtn.type = 'button';
cartBtn.textContent = inStock ? 'Add to Cart' : 'Out of Stock';
cartBtn.disabled = !inStock;
if (inStock) {
cartBtn.addEventListener('click', function() {
cartBtn.textContent = 'Adding...';
cartBtn.disabled = true;
fetch('/cart/add.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ items: [{ id: Number(product.epi), quantity: 1 }] })
})
.then(function(response) {
if (response.ok) {
cartBtn.textContent = 'Added!';
setTimeout(function() {
cartBtn.textContent = 'Add to Cart';
cartBtn.disabled = false;
}, 2000);
} else {
cartBtn.textContent = 'Error';
cartBtn.disabled = false;
}
})
.catch(function() {
cartBtn.textContent = 'Error';
cartBtn.disabled = false;
});
});
}
actions.appendChild(cartBtn);
// Remove
var removeBtn = document.createElement('button');
removeBtn.type = 'button';
removeBtn.textContent = '\u00d7';
removeBtn.setAttribute('aria-label', 'Remove from wishlist');
removeBtn.addEventListener('click', function() {
swat.removeFromWishList(
{ epi: product.epi, empi: product.empi, du: product.du },
function() {
card.remove();
var remaining = container.querySelectorAll('.swym-wishlist-card');
if (countEl) countEl.textContent = '(' + remaining.length + ' items)';
if (remaining.length === 0 && emptyEl) {
emptyEl.style.display = 'block';
}
}
);
});
actions.appendChild(removeBtn);
card.appendChild(actions);
container.appendChild(card);
});
}
function formatPrice(priceInCents) {
return '$' + (priceInCents / 100).toFixed(2);
}
window.SwymCallbacks = window.SwymCallbacks || [];
window.SwymCallbacks.push(initWishlistPage);
})();How It Works
- On SDK ready, checks login state via
swat.platform.isLoggedIn()— shows login nudge for anonymous users swat.fetch()gets all wishlisted products- If empty → shows empty state with browse CTA. If products exist → renders cards
- Save nudge: If anonymous user has 3+ items, shows "Don't lose your list" prompt (once per session, dismissible)
- Remove calls
swat.removeFromWishList(), removes the card from DOM, updates count - Add to Cart POSTs to Shopify's
/cart/add.jswith the variant ID, shows "Added!" feedback - Listens for
sw:removedfromwishlistevents to re-render in real-time
Conversion Features
| Feature | When | What |
|---|---|---|
| Login nudge | Anonymous user visits wishlist page | Soft banner: "Sign in to save your wishlist across devices" |
| Save nudge | Anonymous user has 3+ items | "Don't lose your list! You've saved N items. Sign in to keep them." |
| Empty state | 0 items | "Start adding items you love" with browse CTA |
| Return URL | Login link | Always passes ?return_url=/pages/wishlist so user returns after login |
Why 3 items? This threshold balances intent signals — users with 3+ items have demonstrated enough engagement that a save prompt feels helpful rather than intrusive.
Product Fields Used
Product metadata source of truthThe example below uses product metadata returned by Swym, such as
iu,dt,du,pr, andepi, to render the initial wishlist page. In production, refresh product and variant details from Shopify before relying on them for pricing, availability, display, cart, or checkout flows. Use Shopify's Storefront API ProductVariant object as the source of truth.
| Field | Usage |
|---|---|
iu | Product image |
dt | Product title |
du | Product URL (links) |
pr | Price in cents (÷ 100 for display). Use your store's money format for non-USD |
epi | Variant ID (for cart) |
empi | Product ID |
Styling
The page renders semantic HTML — style it to match your theme's collection page:
| Element | Purpose |
|---|---|
#swym-wishlist-grid | Product grid container — match your collection grid's grid-template-columns, gap |
.swym-wishlist-card | Product card — replace with your theme's card classes |
#swym-login-nudge | Login banner — style as a soft info banner |
#swym-save-nudge | Save prompt — style as an attention/warning banner |
#swym-wishlist-empty | Empty state — center-aligned with browse CTA |
Critical: The product cards should reuse your theme's collection page card structure (same image ratio, typography, price format, grid columns). A shopper should not be able to tell this page was built separately from the collection page.
Updated 13 days ago