API ReferenceGuides
Log In
API Reference

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.json that 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.liquid directly 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

{% 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">&times;</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
  • Gridgrid-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

(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

  1. On SDK ready, checks login state via swat.platform.isLoggedIn() — shows login nudge for anonymous users
  2. swat.fetch() gets all wishlisted products
  3. If empty → shows empty state with browse CTA. If products exist → renders cards
  4. Save nudge: If anonymous user has 3+ items, shows "Don't lose your list" prompt (once per session, dismissible)
  5. Remove calls swat.removeFromWishList(), removes the card from DOM, updates count
  6. Add to Cart POSTs to Shopify's /cart/add.js with the variant ID, shows "Added!" feedback
  7. Listens for sw:removedfromwishlist events to re-render in real-time

Conversion Features

FeatureWhenWhat
Login nudgeAnonymous user visits wishlist pageSoft banner: "Sign in to save your wishlist across devices"
Save nudgeAnonymous user has 3+ items"Don't lose your list! You've saved N items. Sign in to keep them."
Empty state0 items"Start adding items you love" with browse CTA
Return URLLogin linkAlways 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

FieldUsage
iuProduct image
dtProduct title
duProduct URL (links)
prPrice in cents (÷ 100 for display). Use your store's money format for non-USD
epiVariant ID (for cart)
empiProduct ID

Styling

The page renders semantic HTML — style it to match your theme's collection page:

ElementPurpose
#swym-wishlist-gridProduct grid container — match your collection grid's grid-template-columns, gap
.swym-wishlist-cardProduct card — replace with your theme's card classes
#swym-login-nudgeLogin banner — style as a soft info banner
#swym-save-nudgeSave prompt — style as an attention/warning banner
#swym-wishlist-emptyEmpty 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.