Skip to content

Create a powerful and customizable wishlist

This tutorial shows how to implement a reactive wishlist in a Shopify custom app powered by Brush.

View the demo (password: brush)

The wishlist allows customers (as guest or logged-in) to:

  1. Toggle products as favorites with a heart icon on product cards.
  2. View all saved items on a dedicated wishlist page.
  3. See real-time UI updates when adding/removing items.
  4. Persist their selections (per user) using Gadget, while avoiding redundant product data in the DB.

Key point: Instead of storing full product data in Gadget, we store only product IDs and fetch details dynamically using the Shopify AJAX API on the wishlist page. This ensures the DB stays lightweight and always shows fresh product info (pricing, availability, etc.).

See branch on backend tutorial repo tutorial/wishlist and its Wishlist - Part 1 commit.

See branch on frontend tutorial repo tutorial/wishlist and its Wishlist - Part 1 commit.

  • wishlist-toggler-button.liquid

    • Placed on product cards, displays a heart icon.
    • Uses the wishlist-toggler.ts Alpine component to handle clicks.
    • Adds/removes the product ID from the Brush wishlist store.
  • wishlist-list.liquid

    • Renders a dynamic list of wishlist items on a page.
    • Uses WishlistList Alpine component to:
      1. Subscribe to the Brush store for changes.
      2. Fetch product details via Shopify AJAX API.
      3. Render the updated product list dynamically.
  • icon-heart.liquid

    • Reusable SVG icon for hearts.
    • Styled via CSS with hover, active, and “pop” animation.
  • stores/wishlist.ts

    • Reactive store holding the current user’s wishlist IDs.
    • Components subscribe to this store, enabling instant UI updates when items are toggled.
  • services/wishlist-service.ts

    • Acts as the orchestrator:
      1. Receives toggle requests from WishlistToggler.
      2. Updates the Gadget backend.
      3. Updates the Brush store.
      4. Emits events that other components can react to.
    • Pattern highlight: Using a service as a single source of truth keeps components decoupled.
  • Stores only user ID and product IDs — no full product data.
  • Simplifies syncing and prevents storing stale Shopify product info.
  • When the wishlist page is loaded, WishlistList fetches product details from Shopify AJAX API for each saved ID.
  • User clicks the heart button.
  • WishlistToggler calls WishlistService.toggle(productId).
  • WishlistService:
    • Updates Gadget backend.
    • Updates Brush store (wishlistStore).
    • Triggers a reactive event for UI components.
  • Heart icon updates instantly via Alpine reactivity.
  • WishlistList subscribes to wishlistStore.
  • On store change:
    1. Loops through product IDs.
    2. Fetches full product details via Shopify AJAX API (/products/<handle>.js).
    3. Renders them with matching CSS.
  • Benefit: Always up-to-date product info without storing it in Gadget.
  • CSS handles hover, active states, and a “pop” animation when toggled.
  • Maintain atomic styling: keep UI behavior separate from business logic.
  • Gadget ensures wishlist persists per user.
  • Brush store ensures UI reacts immediately.
  • Alpine + events + service pattern avoids tightly coupling DB calls to components.
  • Allow both guest and logged-in wishlists
    The use of Brush stores (powered by AlpineJS persist plugin) allows storing wishlist in LocalStorage for rendering while syncing it to the backend when the user is actually logged-in.

  • Use a single service as orchestrator
    Keeps components clean and decoupled. Any component can subscribe to store events without worrying about DB details.

  • Keep DB lean
    Only store IDs, fetch product details dynamically. Avoids data duplication and stale information.

  • Reactive UI with Brush + Alpine
    Components automatically update when the store changes, no manual DOM manipulation needed.

  • AJAX API for Shopify products
    Ensures wishlist page shows live inventory and pricing. Perfect for stores with frequent updates.

  • Event-driven design
    Components listen to store events, not each other. Makes it easy to add more UI features (e.g., mini wishlist in header) later.