Skip to content

Wishlist

A heart on every product card and on the product page saves a piece for later. Saved products appear in a header drawer and on a full wishlist page. No app, no account, no monthly fee.

Storage is the device’s, not the customer’s account. That’s a deliberate decision with real trade-offs, explained below.

Theme settings → Quick view & wishlist

SettingTypeDefaultWhat it does
Enable wishlistCheckboxOnHearts on cards and product pages, the header count, and the drawer
Wishlist pageURL(empty)The wishlist page. Link it here so the header shows “Saved”

To create the page: Online Store → Pages → Add page, then in the theme editor assign it the wishlist template. Point wishlist_url at it. Until you do, the drawer’s empty-state button and the header heart both fall back to /pages/wishlist.

PlaceWhat the shopper gets
Product cardA heart in the card corner, filled when saved
Product pageA heart beside the add-to-bag button, from the Add to cart block
HeaderA heart with a count, hidden entirely at zero
Wishlist drawerUp to 12 saved pieces as rows, with add-to-bag and remove
Wishlist pageEvery saved piece as a full product card

Hearts are state-synced across the whole page: toggling one on a card immediately updates the same product’s heart in the quick view modal or anywhere else it’s visible.

Why it isn’t synced to the customer account

Section titled “Why it isn’t synced to the customer account”

Because a theme cannot write it.

A wishlist tied to a customer account has to be stored against that customer, and a theme cannot write to a customer’s record. It can read and display what is there, but the Storefront API doesn’t expose customer metafield writes to a theme, and the Admin API requires a server-side credential no theme can safely hold. There is no version of an account-synced wishlist that is implementable in theme code.

So the options are: a real app with a server behind it, or the browser’s own storage. Signal takes the second and is honest about it — the wishlist page says “Saved on this device” rather than implying a sync that isn’t happening.

Signal’s wishlistA wishlist app
CostIncludedMonthly subscription
Works for guestsYes, immediatelyUsually requires an account
Cross-device syncNoYes
Email remindersNoYes
”Back in stock” on saved itemsNoOften
Merchant-visible analyticsNoYes
SetupAlready shippedApp install and configuration
Data leaving the deviceNoneAccount and email

For most drop-driven stores the device-local version is plenty: a shopper who cares enough to save something usually does it on the phone they intend to buy from, inside the same week. If re-engagement email is your actual strategy, install an app — and switch enable_wishlist off so there aren’t two hearts on every card.

Saved products are held by the shopper’s own browser as a simple list of product handles:

["tactical-shell-coal", "ripstop-pack-30l", "balaclava-orange"]

Handles only — no prices, no images, no timestamps. Every surface looks the product up fresh, so a price change, a rename or a sell-out is reflected the next time the list is opened rather than being frozen at the moment of saving.

Click handling is delegated at the document level, which is why hearts work on content injected after page load — load-more results, the quick view modal, recommendation rails.

The header heart opens a drawer rather than navigating. Rows are fetched from /products/{handle}.js each time the drawer opens, capped at 12, so the list is always current and a shopper who never opens it pays nothing at page load.

A handle that answers with a 4xx is the product itself saying it’s been deleted, unpublished or renamed — those are dropped from the saved list. A network failure is not treated the same way, so a bad moment on the connection can’t quietly empty someone’s wishlist.

The heart in the header is a real link to the wishlist page, so it still goes somewhere useful even if scripting is unavailable.

The page doesn’t hand-roll its own row markup. It turns the saved handles into a search query, re-fetches its own section through the Section Rendering API, and lifts out the server-rendered product cards:

/search?type=product&q=handle:tactical-shell-coal OR handle:ripstop-pack-30l&section_id=…

The result is that the wishlist looks exactly like every other product grid in the theme and inherits everything those cards do — badges, hover image swap, quick view, compare, and the heart itself, which doubles as the remove control. The returned set is verified against what was asked for, so a fuzzy search match can never put a product on the page the shopper didn’t save.

Cards are re-ordered into the saved order, newest first.

  • Every heart is a real <button> with aria-pressed reflecting saved state, so a screen reader announces the toggle correctly rather than reading a decorative icon.
  • Its aria-label names the product — “Save Tactical Shell for later” — so a list of hearts isn’t a list of identical “Save” buttons.
  • The drawer is role="dialog" with aria-modal, closes on Escape, and locks page scroll while open.
  • The saved state is a filled icon, not a colour change alone.

Under wishlist in locales/en.default.json: save_product, remove, remove_short, saved_locally, view_all, empty_a, empty_b, empty_text, empty_button.