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)
What the wishlist does
Section titled “What the wishlist does”The wishlist allows customers (as guest or logged-in) to:
- Toggle products as favorites with a heart icon on product cards.
- View all saved items on a dedicated wishlist page.
- See real-time UI updates when adding/removing items.
- 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.).
Follow along with the code
Section titled “Follow along with the code”Backend code
Section titled “Backend code”See branch on backend tutorial repo tutorial/wishlist and its Wishlist - Part 1 commit.
Frontend code
Section titled “Frontend code”See branch on frontend tutorial repo tutorial/wishlist and its Wishlist - Part 1 commit.
Implementation Overview
Section titled “Implementation Overview”1. AlpineJS UI Components
Section titled “1. AlpineJS UI Components”-
wishlist-toggler-button.liquid- Placed on product cards, displays a heart icon.
- Uses the
wishlist-toggler.tsAlpine 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
WishlistListAlpine component to:- Subscribe to the Brush store for changes.
- Fetch product details via Shopify AJAX API.
- Render the updated product list dynamically.
-
icon-heart.liquid- Reusable SVG icon for hearts.
- Styled via CSS with hover, active, and “pop” animation.
2. Brush Stores & Services
Section titled “2. Brush Stores & Services”-
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:
- Receives toggle requests from
WishlistToggler. - Updates the Gadget backend.
- Updates the Brush store.
- Emits events that other components can react to.
- Receives toggle requests from
- Pattern highlight: Using a service as a single source of truth keeps components decoupled.
- Acts as the orchestrator:
3. Gadget Backend
Section titled “3. Gadget Backend”- 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,
WishlistListfetches product details from Shopify AJAX API for each saved ID.
How It Works (Step-by-Step Recipe)
Section titled “How It Works (Step-by-Step Recipe)”1. Heart Toggle
Section titled “1. Heart Toggle”- User clicks the heart button.
WishlistTogglercallsWishlistService.toggle(productId).WishlistService:- Updates Gadget backend.
- Updates Brush store (
wishlistStore). - Triggers a reactive event for UI components.
- Heart icon updates instantly via Alpine reactivity.
2. Wishlist Page
Section titled “2. Wishlist Page”WishlistListsubscribes towishlistStore.- On store change:
- Loops through product IDs.
- Fetches full product details via Shopify AJAX API (
/products/<handle>.js). - Renders them with matching CSS.
- Benefit: Always up-to-date product info without storing it in Gadget.
3. Styling & Animations
Section titled “3. Styling & Animations”- CSS handles hover, active states, and a “pop” animation when toggled.
- Maintain atomic styling: keep UI behavior separate from business logic.
4. Data Persistence
Section titled “4. Data Persistence”- Gadget ensures wishlist persists per user.
- Brush store ensures UI reacts immediately.
- Alpine + events + service pattern avoids tightly coupling DB calls to components.
Best Practices & Powerful Tips
Section titled “Best Practices & Powerful Tips”-
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.