/web-app-features

How to Add Wish List to Your Web App

Learn how to easily add a wish list feature to your web app and boost user engagement with our step-by-step guide.

Book a free  consultation
4.9
Clutch rating 🌟
600+
Happy partners
17+
Countries served
190+
Team members
Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

How to Add Wish List to Your Web App

How to Add a Wish List Feature to Your Web App

 

Why Wish Lists Matter for Your Business

 

A well-implemented wish list feature does more than just let users bookmark items—it's a powerful conversion tool that provides valuable data about customer desires while reducing cart abandonment. When users can save items for later instead of abandoning them entirely, you maintain a connection to potential purchases that might otherwise be lost.

 

Core Components of an Effective Wish List System

 

1. Data Structure Considerations

 

At its core, a wish list is a many-to-many relationship between users and products. Let's break down what this means in database terms:

 

-- Basic wishlist table structure
CREATE TABLE wishlists (
  id INT PRIMARY KEY AUTO_INCREMENT,
  user_id INT NOT NULL,
  name VARCHAR(255) DEFAULT 'My Wishlist', -- Allow users to name their lists
  is_public BOOLEAN DEFAULT FALSE,         -- Privacy controls
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE TABLE wishlist_items (
  wishlist_id INT NOT NULL,
  product_id INT NOT NULL,
  added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (wishlist_id, product_id),   // Composite key prevents duplicates
  FOREIGN KEY (wishlist_id) REFERENCES wishlists(id) ON DELETE CASCADE,
  FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE
);

 

This structure supports multiple wish lists per user and includes timestamps for analytics purposes.

 

2. Backend Implementation

 

Your API endpoints should handle the core CRUD operations. Here's a RESTful approach using Express.js:

 

// routes/wishlist.js
const express = require('express');
const router = express.Router();
const auth = require('../middleware/auth');

// Get all wishlists for the current user
router.get('/', auth, async (req, res) => {
  try {
    const wishlists = await db.query(
      'SELECT * FROM wishlists WHERE user_id = ?',
      [req.user.id]
    );
    res.json(wishlists);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// Add item to wishlist
router.post('/:wishlistId/items', auth, async (req, res) => {
  const { wishlistId } = req.params;
  const { productId } = req.body;
  
  try {
    // First verify the wishlist belongs to the user
    const wishlist = await db.query(
      'SELECT * FROM wishlists WHERE id = ? AND user_id = ?', 
      [wishlistId, req.user.id]
    );
    
    if (wishlist.length === 0) {
      return res.status(404).json({ message: 'Wishlist not found' });
    }
    
    // Add item to wishlist
    await db.query(
      'INSERT INTO wishlist_items (wishlist_id, product_id) VALUES (?, ?)',
      [wishlistId, productId]
    );
    
    res.status(201).json({ message: 'Item added to wishlist' });
  } catch (err) {
    // Handle duplicate entries gracefully
    if (err.code === 'ER_DUP_ENTRY') {
      return res.status(400).json({ message: 'Item already in wishlist' });
    }
    res.status(500).json({ message: err.message });
  }
});

// Remove item from wishlist
router.delete('/:wishlistId/items/:productId', auth, async (req, res) => {
  // Similar implementation with proper authorization checks
});

module.exports = router;

 

3. Frontend Implementation

 

The UI component needs to be both intuitive and visually consistent with your brand. Here's a React implementation:

 

// WishlistButton.jsx
import React, { useState, useEffect } from 'react';
import { useAuth } from '../contexts/AuthContext';
import { HeartIcon, HeartFilledIcon } from '../components/Icons';

const WishlistButton = ({ productId }) => {
  const { user, isAuthenticated } = useAuth();
  const [isInWishlist, setIsInWishlist] = useState(false);
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    // Check if item is in wishlist when component mounts
    if (isAuthenticated) {
      checkWishlistStatus();
    }
  }, [productId, isAuthenticated]);

  const checkWishlistStatus = async () => {
    try {
      const response = await fetch(`/api/wishlists/default/items/${productId}`);
      setIsInWishlist(response.ok);
    } catch (error) {
      console.error('Error checking wishlist status:', error);
    }
  };

  const toggleWishlist = async () => {
    if (!isAuthenticated) {
      // Prompt for login or save to local storage
      return window.location.href = '/login?redirect=' + window.location.pathname;
    }

    setIsLoading(true);
    
    try {
      if (isInWishlist) {
        // Remove from wishlist
        await fetch(`/api/wishlists/default/items/${productId}`, {
          method: 'DELETE',
          headers: { 'Content-Type': 'application/json' }
        });
      } else {
        // Add to wishlist
        await fetch(`/api/wishlists/default/items`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ productId })
        });
      }
      
      setIsInWishlist(!isInWishlist);
    } catch (error) {
      console.error('Error updating wishlist:', error);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <button 
      onClick={toggleWishlist}
      disabled={isLoading}
      className={`wishlist-btn ${isInWishlist ? 'active' : ''}`}
      aria-label={isInWishlist ? "Remove from wishlist" : "Add to wishlist"}
    >
      {isInWishlist ? <HeartFilledIcon /> : <HeartIcon />}
    </button>
  );
};

export default WishlistButton;

 

Guest Users and Persistence Strategies

 

Handling Anonymous Users

 

Don't lose potential data just because users aren't logged in. Implement local storage for guest wish lists:

 

// wishlistService.js
export const addToWishlist = (productId) => {
  if (isAuthenticated()) {
    // API call to backend
    return apiAddToWishlist(productId);
  } else {
    // Store in localStorage
    const wishlist = JSON.parse(localStorage.getItem('guestWishlist') || '[]');
    if (!wishlist.includes(productId)) {
      wishlist.push(productId);
      localStorage.setItem('guestWishlist', JSON.stringify(wishlist));
    }
    return Promise.resolve();
  }
};

// During user login, migrate local wishlist to database
export const migrateGuestWishlist = async () => {
  const guestWishlist = JSON.parse(localStorage.getItem('guestWishlist') || '[]');
  
  if (guestWishlist.length > 0) {
    // Add items to user's wishlist via API
    await Promise.all(guestWishlist.map(productId => apiAddToWishlist(productId)));
    
    // Clear local storage
    localStorage.removeItem('guestWishlist');
  }
};

 

Advanced Features That Drive Conversions

 

Price Drop Notifications

 

One of the most powerful ways to convert wish list items to sales is with price alerts:

 

// In your product price update hook or job
const notifyPriceDrops = async (product) => {
  // Find all users who have this product in their wishlist
  const users = await db.query(`
    SELECT DISTINCT u.id, u.email, u.notification_preferences 
    FROM users u
    JOIN wishlists w ON u.id = w.user_id
    JOIN wishlist_items wi ON w.id = wi.wishlist_id
    WHERE wi.product_id = ? AND u.notification_preferences->'$.price_alerts' = true
  `, [product.id]);
  
  // Send notifications
  users.forEach(user => {
    notificationService.sendPriceDropAlert({
      userId: user.id,
      email: user.email,
      product: {
        id: product.id,
        name: product.name,
        oldPrice: product.previous_price,
        newPrice: product.current_price,
        imageUrl: product.image_url,
        url: `${process.env.SITE_URL}/products/${product.slug}`
      }
    });
  });
};

 

Social Sharing

 

Enable users to share wish lists for special occasions:

 

// ShareWishlistModal.jsx
const ShareWishlistModal = ({ wishlistId, isOpen, onClose }) => {
  const [shareUrl, setShareUrl] = useState('');
  const [isPublic, setIsPublic] = useState(false);
  
  useEffect(() => {
    // Fetch wishlist details including public status
    const fetchWishlistDetails = async () => {
      const response = await fetch(`/api/wishlists/${wishlistId}`);
      const data = await response.json();
      setIsPublic(data.is_public);
      
      if (data.is_public) {
        setShareUrl(`${window.location.origin}/wishlists/shared/${wishlistId}`);
      }
    };
    
    if (isOpen && wishlistId) {
      fetchWishlistDetails();
    }
  }, [isOpen, wishlistId]);
  
  const togglePublicStatus = async () => {
    // Toggle wishlist privacy setting
    const response = await fetch(`/api/wishlists/${wishlistId}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ is_public: !isPublic })
    });
    
    if (response.ok) {
      const newStatus = !isPublic;
      setIsPublic(newStatus);
      if (newStatus) {
        setShareUrl(`${window.location.origin}/wishlists/shared/${wishlistId}`);
      } else {
        setShareUrl('');
      }
    }
  };
  
  return (
    <Modal isOpen={isOpen} onClose={onClose}>
      <h3>Share Your Wishlist</h3>
      
      <div className="privacy-toggle">
        <label>
          <input 
            type="checkbox" 
            checked={isPublic} 
            onChange={togglePublicStatus} 
          />
          Make this wishlist public
        </label>
      </div>
      
      {isPublic && (
        <div className="share-options">
          <input 
            type="text" 
            value={shareUrl} 
            readOnly 
            onClick={(e) => e.target.select()} 
          />
          
          <div className="social-buttons">
            <button onClick={() => window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`)}>
              Share on Facebook
            </button>
            <button onClick={() => window.open(`https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=Check out my wishlist!`)}>
              Share on Twitter
            </button>
            <button onClick={() => navigator.clipboard.writeText(shareUrl)}>
              Copy Link
            </button>
          </div>
        </div>
      )}
    </Modal>
  );
};

 

Performance Optimization

 

Caching Strategy

 

Wish lists are read frequently but updated less often, making them perfect for caching:

 

// middleware/wishlistCache.js
const redis = require('redis');
const client = redis.createClient(process.env.REDIS_URL);

const CACHE_TTL = 3600; // 1 hour in seconds

const wishlistCache = async (req, res, next) => {
  // Only cache GET requests
  if (req.method !== 'GET') return next();
  
  const userId = req.user.id;
  const cacheKey = `wishlist:${userId}`;
  
  try {
    // Try to get from cache
    client.get(cacheKey, (err, data) => {
      if (err) throw err;
      
      if (data) {
        // Cache hit
        return res.json(JSON.parse(data));
      }
      
      // Cache miss - store original res.json method
      const originalJson = res.json;
      
      // Override res.json method to cache response before sending
      res.json = function(body) {
        client.setex(cacheKey, CACHE_TTL, JSON.stringify(body));
        return originalJson.call(this, body);
      };
      
      next();
    });
  } catch (err) {
    // If caching fails, just continue without caching
    console.error('Cache error:', err);
    next();
  }
};

// Invalidate cache when wishlist is modified
const invalidateWishlistCache = (userId) => {
  const cacheKey = `wishlist:${userId}`;
  client.del(cacheKey);
};

module.exports = { wishlistCache, invalidateWishlistCache };

 

Database Indexing

 

Ensure your wishlist queries perform well with proper indexes:

 

-- Create indexes for frequent wishlist operations
CREATE INDEX idx_wishlists_user_id ON wishlists(user_id);
CREATE INDEX idx_wishlist_items_product_id ON wishlist_items(product_id);

 

Analytics Integration

 

Tracking Wish List Conversion Rates

 

Monitor how wish lists affect your conversion funnel:

 

// In your analytics service
const trackWishlistEvent = (eventType, data) => {
  switch (eventType) {
    case 'add_to_wishlist':
      analytics.track('Add to Wishlist', {
        product_id: data.productId,
        product_name: data.productName,
        product_price: data.price,
        category: data.category,
        wishlist_id: data.wishlistId,
        source: data.source // 'product_page', 'category_page', etc.
      });
      break;
      
    case 'remove_from_wishlist':
      analytics.track('Remove from Wishlist', {
        product_id: data.productId,
        wishlist_id: data.wishlistId,
        days_in_wishlist: data.daysInWishlist
      });
      break;
      
    case 'add_to_cart_from_wishlist':
      analytics.track('Add to Cart from Wishlist', {
        product_id: data.productId,
        wishlist_id: data.wishlistId,
        days_in_wishlist: data.daysInWishlist
      });
      break;
  }
};

 

Implementation Timeline and Resource Planning

 

Phased Approach

 

  • Phase 1 (1-2 weeks): Basic wish list functionality with database structure and simple UI
  • Phase 2 (1-2 weeks): Guest user functionality, wishlist persistence
  • Phase 3 (1-2 weeks): Advanced features like social sharing and price alerts
  • Phase 4 (1 week): Analytics integration and performance optimization

 

Common Pitfalls

 

  • Forgetting to handle product inventory changes (items going out of stock)
  • Neglecting mobile UX for wish list interactions
  • Not optimizing database queries for large wish lists
  • Missing opportunities to re-engage users with wish list email reminders

 

Testing Your Wish List Implementation

 

Key Test Cases

 

// wishlist.test.js
describe('Wishlist Functionality', () => {
  test('Should add item to wishlist when authenticated', async () => {
    // Setup test user and authentication
    const user = await createTestUser();
    const token = generateAuthToken(user);
    
    // Test API endpoint
    const response = await request(app)
      .post('/api/wishlists/1/items')
      .set('Authorization', `Bearer ${token}`)
      .send({ productId: 123 });
      
    expect(response.statusCode).toBe(201);
    
    // Verify database state
    const wishlistItem = await db.query(
      'SELECT * FROM wishlist_items WHERE wishlist_id = ? AND product_id = ?',
      [1, 123]
    );
    expect(wishlistItem.length).toBe(1);
  });
  
  test('Should store wishlist items in localStorage for guest users', async () => {
    // Setup test environment
    localStorage.clear();
    
    // Execute the function
    await addToWishlist(456);
    
    // Verify localStorage state
    const wishlist = JSON.parse(localStorage.getItem('guestWishlist'));
    expect(wishlist).toContain(456);
  });
  
  test('Should migrate guest wishlist to user wishlist after login', async () => {
    // Setup
    localStorage.setItem('guestWishlist', JSON.stringify([789, 101]));
    const user = await createTestUser();
    
    // Simulate login and migration
    await loginUser(user);
    await migrateGuestWishlist();
    
    // Verify migration
    const userWishlistItems = await db.query(
      'SELECT product_id FROM wishlist_items JOIN wishlists ON wishlist_items.wishlist_id = wishlists.id WHERE wishlists.user_id = ?',
      [user.id]
    );
    
    expect(userWishlistItems.map(item => item.product_id)).toContain(789);
    expect(userWishlistItems.map(item => item.product_id)).toContain(101);
    expect(localStorage.getItem('guestWishlist')).toBeNull();
  });
});

 

Conclusion

 

A well-implemented wish list feature sits at the intersection of user experience and business goals. By building a system that's persistent, performant, and properly integrated with your analytics, you create more than just a bookmark feature—you establish a conversion pipeline that turns browsing into buying.

 

Remember that the best wish list implementations adapt to how your specific customers shop. For some businesses, multiple lists with organization features make sense; for others, a simple save-for-later function is sufficient. Let your users' behavior guide your implementation priorities while ensuring the technical foundation remains solid.

Ship Wish List 10x Faster with RapidDev

Connect with our team to unlock the full potential of code solutions with a no-commitment consultation!

Book a Free Consultation

Top 3 Wish List Usecases

Explore the top 3 wish list use cases to boost user engagement and sales in your web app.

 

Strategic Customer Retention

 

A wish list creates an intentional "digital waiting room" for future purchases, keeping customers engaged with your brand between transactions and providing valuable insights into consumer desires before they're ready to buy.

 

  • Reduces cart abandonment by giving shoppers a legitimate place to store items they're interested in but not ready to purchase immediately, maintaining their connection to your product ecosystem.
  • Creates natural re-engagement opportunities through targeted notifications about price drops, limited availability, or complementary items related to wish-listed products.
  • Provides predictive demand signals that help merchandising teams anticipate interest in specific products before purchase conversion, improving inventory planning.

 

Social Proof Amplification

 

When implemented with social sharing capabilities, wish lists transform private consumer intentions into public endorsements, extending your product visibility beyond traditional marketing channels.

 

  • Facilitates organic product discovery when users share wish lists with friends and family, effectively turning customers into brand ambassadors through their curated selections.
  • Drives contextual gift-giving for special occasions, allowing gift-givers to purchase directly from recipients' lists, increasing conversion on items with demonstrated interest.
  • Creates user-generated collections that can highlight product versatility and appeal across different customer segments more authentically than traditional categorization.

 

Personalization Engine Fuel

 

Wish lists provide explicit preference data that significantly enhances recommendation algorithms, allowing for more sophisticated personalization than browsing or purchase history alone can provide.

 

  • Captures aspirational interests that may not be reflected in purchase history, revealing customer desires rather than just their completed transactions.
  • Refines price sensitivity insights by revealing which products customers desire but haven't purchased, potentially indicating price threshold barriers.
  • Enables cross-category intelligence by revealing relationships between product categories in customers' desired items, informing bundle opportunities and complementary product development.


Recognized by the best

Trusted by 600+ businesses globally

From startups to enterprises and everything in between, see for yourself our incredible impact.

RapidDev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with.

They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

Arkady
CPO, Praction
Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost.

He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Donald Muir
Co-Founder, Arc
RapidDev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space.

They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Mat Westergreen-Thorne
Co-CEO, Grantify
RapidDev is an excellent developer for custom-code solutions.

We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Emmanuel Brown
Co-Founder, Church Real Estate Marketplace
Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 

This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Samantha Fekete
Production Manager, Media Production Company
The pSEO strategy executed by RapidDev is clearly driving meaningful results.

Working with RapidDev has delivered measurable, year-over-year growth. Comparing the same period, clicks increased by 129%, impressions grew by 196%, and average position improved by 14.6%. Most importantly, qualified contact form submissions rose 350%, excluding spam.

Appreciation as well to Matt Graham for championing the collaboration!

Michael W. Hammond
Principal Owner, OCD Tech

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.Â