Lantern

Custom Wishlist Implementation

Learn how to build custom wishlist functionality using Lantern's JavaScript API and metafields

For developers who want to build custom wishlist experiences beyond Lantern's built-in extensions, this guide shows you how to implement wishlist functionality using Lantern's JavaScript API and metafields.

Prerequisites

Before building custom wishlist functionality, ensure you have:

  • Wishlists enabled in your Lantern app
  • Basic knowledge of JavaScript and Liquid templating
  • Access to your theme's code

Accessing Wishlist Data

Customer Metafields

Lantern stores wishlist data in customer metafields that you can access in your Liquid templates

  • customer.metafields.lantern.wishlist - Array of product variant references in the customer's wishlist

JS API

Lantern also stores the wishlist data and functionality in the window.lantern object. You can access it using the following methods:

  • window.lantern.wishlist - Array of product variant references in the customer's wishlist
  • window.lantern.api.toggleListItem - Function to add or remove a product variant from the wishlist

Adding a Custom "Add to Wishlist" Button

To create your own button to add or remove a product variant from the wishlist, add the following code snippet to your theme's Liquid template:

<button
  type="button"
  class="wishlist-toggle"
  data-product-variant-id="{{ product.selected_or_first_available_variant.id }}"
  onclick="toggleWishlist(this)"
>
  <span class="wishlist-add-text">Add to Wishlist</span>
  <span class="wishlist-remove-text">Remove from Wishlist</span>
</button>
 
<script>
  async function toggleWishlist(button) {
    // Check if user is logged in
    if (!window.lantern?.customer?.id) {
      alert('Please log in to add items to your wishlist.');
      return;
    }
 
    // Check if Lantern API is available
    if (!window.lantern?.api?.toggleListItem) {
      console.error('Lantern API not available');
      return;
    }
 
    const variantId = button.dataset.productVariantId;
 
    // Disable button during API call
    button.disabled = true;
 
    try {
      // Call the API and get the updated wishlist back
      const updatedWishlist = await window.lantern.api.toggleListItem({
        productVariantId: variantId
      });
 
      // Update button state based on the returned wishlist
      updateWishlistButton(button, updatedWishlist);
 
    } catch (error) {
      console.error('Error toggling wishlist item:', error);
      alert('There was an error updating your wishlist. Please try again.');
    } finally {
      button.disabled = false;
    }
  }
 
  function updateWishlistButton(button, wishlist = null) {
    const variantId = button.dataset.productVariantId;
 
    // Use provided wishlist or fall back to window.lantern.customer.wishlist
    const currentWishlist = wishlist || window.lantern?.customer?.wishlist || [];
 
    // Check if this variant is in the wishlist
    const isInWishlist = currentWishlist.some(item => item.id === variantId);
 
    // Update button appearance
    if (isInWishlist) {
      button.classList.add('is-favorited');
    } else {
      button.classList.remove('is-favorited');
    }
  }
 
  // Initialize button state when Lantern is ready
  function initWishlistButtons() {
    const buttons = document.querySelectorAll('.wishlist-toggle');
    if (buttons.length > 0 && window.lantern?.customer?.wishlist) {
      buttons.forEach(button => updateWishlistButton(button));
    }
  }
 
  // Initialize when DOM is ready
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initWishlistButtons);
  } else {
    initWishlistButtons();
  }
 
  // Also initialize when Lantern loads (if it's not ready yet)
  if (!window.lantern?.customer) {
    let attempts = 0;
    const maxAttempts = 50; // Poll for 5 seconds
    const checkLantern = setInterval(() => {
      attempts++;
      if (window.lantern?.customer || attempts >= maxAttempts) {
        clearInterval(checkLantern);
        // If customer object is now available, initialize buttons
        if (window.lantern?.customer) {
          initWishlistButtons();
        }
      }
    }, 100);
  }
</script>
 
<style>
  .wishlist-toggle {
    padding: 10px 16px;
    border: 1px solid #ccc;
    background: white;
    cursor: pointer;
    border-radius: 4px;
  }
 
  .wishlist-toggle:disabled {
    opacity: 0.6;
    cursor: not-allowed;
  }
 
  .wishlist-toggle.is-favorited {
    background: #f0f0f0;
    border-color: #999;
  }
 
  .wishlist-remove-text {
    display: none;
  }
 
  .wishlist-remove-text,
  .wishlist-toggle.is-favorited .wishlist-add-text {
    display: none;
  }
 
  .wishlist-toggle.is-favorited .wishlist-remove-text {
    display: inline;
  }
</style>

Rendering Wishlist Items

Creating a Wishlist Section

Create a custom section template (sections/wishlist.liquid) to display saved items using your themes built in product grid snippet (card-product for a Dawn based theme, like in this example).

<div class="collection-list-wrapper isolate section-{{ section.id }}-padding">
  <h2 class="wishlist__heading {{ section.settings.heading_size }}">
    {{ section.settings.title | escape }}
  </h2>
  {% if customer %}
    {% assign wishlist = customer.metafields.lantern.wishlist.value %}
    {% if wishlist and wishlist.size > 0 %}
      <ul
        class="grid product-grid grid--4-col-desktop grid--2-col-tablet-down"
        role="list"
      >
        {% for variant in wishlist %}
          {% assign product = variant.product %}
          <li class="grid__item">
            {% render 'card-product',
              card_product: product,
              variant: variant,
              media_aspect_ratio: section.settings.image_ratio,
              show_secondary_image: section.settings.show_secondary_image,
              show_vendor: true
            %}
          </li>
        {% endfor %}
      </ul>
    {% else %}
      <p>Your wishlist is empty.</p>
    {% endif %}
  {% else %}
    <div class="isolate page-width">
      <div class="rich-text content-container section-{{ section.id }}-padding">
        <div class="rich-text__wrapper rich-text__wrapper--{{ section.settings.desktop_content_position }}{% unless section.settings.full_width %} page-width{% endunless %}">
          <p class="wishlist-login"><a href="{{ routes.account_login_url }}">Log in</a> to view your wishlist.</p>
        </div>
      </div>
    </div>
  {% endif %}
</div>
 
{% schema %}
{
  "name": "Wishlist",
  "tag": "section",
  "class": "section",
  "disabled_on": {
    "groups": ["header", "footer"]
  },
  "settings": [
    {
      "type": "text",
      "id": "title",
      "default": "Wishlist",
      "label": "Title"
    },
    {
      "type": "select",
      "id": "heading_size",
      "options": [
        {
          "value": "h2",
          "label": "t:sections.all.heading_size.options__1.label"
        },
        {
          "value": "h1",
          "label": "t:sections.all.heading_size.options__2.label"
        },
        {
          "value": "h0",
          "label": "t:sections.all.heading_size.options__3.label"
        }
      ],
      "default": "h1",
      "label": "t:sections.all.heading_size.label"
    },
    {
      "type": "header",
      "content": "t:sections.all.padding.section_padding_heading"
      }
  ]
}
{% endschema %}

On this page