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

📘

Product metadata source of truth

The example below uses product metadata returned by Swym, such as iu, dt, du, pr, and epi, 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.

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.