How to Add a Wishlist to Shopify Without an App

2026-09-18 · page builders

You can add a functional wishlist to Shopify without paying a monthly app fee. The trade-off is setup time: you'll create a page template, wire up a button, and store saved products in customer metafields. Budget an afternoon for a first build. This guide walks through the five steps in order, using Liquid, the Shopify Ajax API, and metafields — all available on every Shopify plan.

Why Build a Native Shopify Wishlist Instead of Paying for an App

Wishlist apps typically charge a recurring monthly fee, and many inject their own scripts, styles, and markup into your theme. That matters for two reasons:

There's also a data-ownership argument. A native wishlist stores data in Shopify metafields and customer tags, so it lives inside your store. If you switch themes or stop paying for an app, your data stays put.

The honest counterpoint: apps handle edge cases — guest wishlists, email reminders, cross-device sync for logged-out visitors — that take real engineering to replicate. If you need those, an app is the faster path. The native approach below covers the core case well: logged-in customers saving products and returning to buy them.

What You'll Need Before You Start (Theme Access and a Backup)

Before touching any theme code:

One structural note before you start: this build assumes your theme uses JSON templates (the default in Online Store 2.0 themes like Dawn). If you're on an older theme with .liquid templates, the steps still work but you'll create the template file directly rather than through the theme editor.

Step 1: Create a Wishlist Page Template in Your Theme

  1. In your duplicated theme, go to Online Store → Themes → Customize.
  2. In the top bar, open the Pages dropdown and choose Add page — or create the page first under Online Store → Pages → Add page, titled "Wishlist", with handle wishlist.
  3. Back in the theme editor, click Add template in the template selector, choose page as the base, and name it wishlist. Shopify creates templates/page.wishlist.json.
  4. In the theme code editor (Themes → Edit code), find the new JSON template. It will reference a section — create sections/wishlist-page.liquid and point the template at it.

Inside sections/wishlist-page.liquid, start with a container and a schema block so merchants can edit the heading:

<div class="wishlist-page" data-wishlist-page>
  <h1>{{ section.settings.heading }}</h1>
  <div class="wishlist-grid" data-wishlist-grid></div>
  <p class="wishlist-empty" data-wishlist-empty hidden>
    {{ section.settings.empty_text }}
  </p>
</div>

{% schema %}
{
  "name": "Wishlist",
  "settings": [
    { "type": "text", "id": "heading", "label": "Heading", "default": "My Wishlist" },
    { "type": "text", "id": "empty_text", "label": "Empty message", "default": "You haven't saved any products yet." }
  ],
  "presets": [{ "name": "Wishlist" }]
}
{% endschema %}

Assign this template to the Wishlist page under Online Store → Pages → Wishlist → Theme template.

Step 2: Add the Wishlist Button to Product Pages and Collection Cards

Add a button to sections/main-product.liquid (or your theme's product form section) and to your product card snippet, usually snippets/product-card.liquid or snippets/card-product.liquid.

<button
  type="button"
  class="wishlist-toggle"
  data-wishlist-toggle
  data-product-id="{{ product.id }}"
  data-product-handle="{{ product.handle }}"
  aria-pressed="false"
  aria-label="Save {{ product.title }} to wishlist">
  <span class="wishlist-icon" aria-hidden="true">♡</span>
  <span class="wishlist-label">Save</span>
</button>

Two practical notes:

For collection cards, the same markup works — just make sure product is in scope inside your {% for product in collection.products %} loop.

Step 3: Store Wishlist Items with Metafields and Customer Tags

For logged-in customers, metafields are the right storage layer. Create a customer metafield definition under Settings → Custom data → Customers → Add definition:

Setting Value
Namespace and key custom.wishlist
Type List of single line text
Storefront access Enabled (if you plan to read it client-side)
Description Product handles saved to the customer's wishlist

Then handle the save action in JavaScript. The simplest reliable pattern is to POST to your own app proxy or a Shopify Function endpoint — but for a no-app build, a small script that calls /account endpoints or uses the Storefront API with a customer access token works. If you want to avoid customer authentication entirely, use localStorage as a fallback and sync to metafields on login.

Here's the client-side toggle logic:

document.addEventListener('click', async (e) => {
  const btn = e.target.closest('[data-wishlist-toggle]');
  if (!btn) return;
  const id = btn.dataset.productId;
  const handle = btn.dataset.productHandle;
  const saved = JSON.parse(localStorage.getItem('wishlist') || '[]');
  const index = saved.indexOf(handle);
  if (index > -1) {
    saved.splice(index, 1);
    btn.setAttribute('aria-pressed', 'false');
  } else {
    saved.push(handle);
    btn.setAttribute('aria-pressed', 'true');
  }
  localStorage.setItem('wishlist', JSON.stringify(saved));
});

If you're using customer metafields, mirror this write to the metafield via your authenticated endpoint so the wishlist survives a browser change.

Step 4: Build the Wishlist Page Logic to Display Saved Products

On page load in sections/wishlist-page.liquid, read the saved handles and render product cards. You have two options:

A hybrid works well: render server-side on first load, then update client-side. For most stores under a few hundred wishlist items, the client-side fetch is fine. Cap the number of fetches to avoid a slow page if someone has saved 200 products.

const handles = JSON.parse(localStorage.getItem('wishlist') || '[]');
const grid = document.querySelector('[data-wishlist-grid]');
if (!handles.length) {
  document.querySelector('[data-wishlist-empty]').hidden = false;
} else {
  for (const handle of handles.slice(0, 50)) {
    const res = await fetch(`/products/${handle}.js`);
    const product = await res.json();
    // build and append a card using product.title, product.featured_image, product.price
  }
}

Step 5: Style the Wishlist and Test It Across Devices

Style the grid with CSS Grid and make the toggle button's pressed state obvious:

.wishlist-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1.5rem;
}
.wishlist-toggle[aria-pressed="true"] .wishlist-icon {
  color: #d6336c;
}

Then test these cases before publishing:

Turning Wishlists into Repeat Purchases (and When an App Still Makes Sense)

A wishlist only earns money if you bring people back to it. Three tactics that work without any app:

Shopify Flow is included on higher plans; check current pricing for your plan tier before building automations on it.

When does an app still make sense? Choose one if you need guest wishlists with cross-device sync, automated back-in-stock or price-drop emails out of the box, or wishlist sharing via link. Those features require backend infrastructure — a database, scheduled jobs, email sending — that a theme-only build can't provide. For a store doing under a few hundred orders a month, the native build usually covers 80% of the value at 0% of the recurring cost.

Conclusion

A native Shopify wishlist is five connected pieces: a page template, a toggle button, a storage layer, a render loop, and styling. None of them are exotic, and all of them use Shopify features available on every plan. Build it on a duplicated theme, test the logged-out and logged-in paths, and you'll have a wishlist you fully control. If you later need guest sync or automated reminders, you can add an app on top without throwing away the work.

FAQ

Will this work on any Shopify theme? It works on Online Store 2.0 themes that use JSON templates, including Dawn and most paid themes released since 2021. Older themes with .liquid templates need the same code but a different file structure — you'll create templates/page.wishlist.liquid directly instead of a JSON template.

Do wishlists work for customers who aren't logged in? Yes, using localStorage. The limitation is that the list is tied to one browser on one device. It won't follow the customer to their phone or another browser unless they log in and you sync to customer metafields.

How many products can a customer save before performance suffers? The client-side approach fetches one request per product, so cap the render at 50 items and add a "load more" button beyond that. Server-side rendering with metafields handles larger lists better because it's a single page load.