Skip to main content
RapidDev - Software Development Agency
App Featurespayments-commerce16 min read

How to Add a Wish List to Your App (Copy-Paste Prompts Included)

A wish list needs a heart-toggle button with optimistic UI, a Supabase table with a UNIQUE constraint to prevent duplicates, a dedicated list page, and a share-link flow. With Lovable or V0 you can ship this in 1–2 hours for $0–$25/month. The two pieces AI tools consistently miss are merging the guest localStorage list on login and deduplicating price-drop notification emails.

4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members

Feature spec

Beginner

Category

payments-commerce

Build with AI

1–2 hours with Lovable or V0

Custom build

2–4 days custom dev

Running cost

$0–$25/mo up to 10K users

Works on

Web

Everything it takes to ship a Wish List — parts, prompts, and real costs.

TL;DR

A wish list needs a heart-toggle button with optimistic UI, a Supabase table with a UNIQUE constraint to prevent duplicates, a dedicated list page, and a share-link flow. With Lovable or V0 you can ship this in 1–2 hours for $0–$25/month. The two pieces AI tools consistently miss are merging the guest localStorage list on login and deduplicating price-drop notification emails.

What a Wish List Feature Actually Is

A wish list lets shoppers bookmark products they want without committing to a purchase — the digital equivalent of a sticky note on a shelf. Single-tap save from a product page, a dedicated list to revisit later, and the ability to share it with others are the core three behaviours. Beyond those, the feature earns its keep through price-drop notifications that pull users back to convert, and a guest-to-logged-in merge flow that prevents the frustration of a cleared list after sign-up. The data model is deliberately simple — one join table — but the edge cases around guest sessions, share-link privacy, and notification deduplication are where first builds fall apart.

What users consider table stakes in 2026

  • Heart/bookmark icon toggles instantly on tap with no visible loading delay (optimistic UI)
  • Dedicated wish list page with product grid, empty state, and sort by price or date added
  • Share wish list via a unique URL that works without the recipient logging in
  • Price drop notification email or in-app alert when a saved item's price decreases
  • Items count badge visible on the wish list nav icon so users remember what they saved
  • One-tap 'Move to cart' action from the wish list page — no need to re-navigate to the product

Anatomy of the Feature

Five components, all of which AI tools can generate — the heart toggle and list page are routine; the share link and price-drop cron are where explicit prompting is required.

Layers:UIDataBackend

Heart / save toggle button

UI

A shadcn/ui Button with a Heart icon from lucide-react. On click it fires an optimistic UI update — fills the heart immediately in local state — then inserts or deletes the wish_list_items row in Supabase in the background. Reverting the optimistic state on error keeps the UI honest.

Note: Include ARIA-label='Add to wish list' / 'Remove from wish list' for accessibility. The toggle should read the user's existing wish list on page load to show correct filled/empty state.

Wish list page

UI

A product grid rendering saved items fetched from wish_list_items joined with the products table. Requires an empty state with a 'Start exploring' CTA that links to the product catalogue. Supports sort by date added (default) and price, and individual item removal.

Note: Show a skeleton loader while the list fetches — a flash of empty state before data arrives confuses users into thinking their list is gone.

wish_list_items table

Data

Supabase PostgreSQL table with columns user_id, product_id, saved_price_cents, and added_at. A UNIQUE(user_id, product_id) constraint prevents duplicates at the database level. RLS policies restrict SELECT/INSERT/DELETE to the row owner.

Note: saved_price_cents captures the price at save time, enabling the price drop check to compare against the current price without a separate price history table.

Share link backend

Backend

A shared_wish_lists table stores a share_token (UUID generated by gen_random_uuid()) per user. A public Supabase Edge Function or Next.js API route accepts GET /wish-list/share/[token] and returns product_id, name, price, and image_url — never user_id or email. The public RLS policy allows SELECT on wish_list_items WHERE a valid share_token matches.

Note: nanoid is an equally valid token generator; gen_random_uuid() requires no extra dependency. Always sanitise the public endpoint response to exclude internal IDs.

Price drop notification cron

Backend

A scheduled Supabase Edge Function runs daily or hourly, compares products.price_cents against wish_list_items.saved_price_cents for all active wish list rows, and sends an email via Resend for each price decrease. The last_notified_at column prevents repeated emails for items that stay at their lowered price.

Note: The cron trigger is configured in the Supabase Dashboard under Edge Functions, not in code. Prompt AI tools to scaffold the function body, then set the schedule manually.

The data model

Three tables cover wish list persistence, sharing, and price-drop deduplication. Paste this into the Supabase SQL editor and run it before prompting your AI tool.

schema.sql
1create table public.wish_list_items (
2 id uuid primary key default gen_random_uuid(),
3 user_id uuid references auth.users(id) on delete cascade not null,
4 product_id uuid references public.products(id) on delete cascade not null,
5 saved_price_cents int not null,
6 last_notified_at timestamptz,
7 added_at timestamptz not null default now(),
8 constraint wish_list_items_unique_user_product unique (user_id, product_id)
9);
10
11alter table public.wish_list_items enable row level security;
12
13create policy "Users can view own wish list"
14 on public.wish_list_items for select
15 using (auth.uid() = user_id);
16
17create policy "Users can insert own wish list items"
18 on public.wish_list_items for insert
19 with check (auth.uid() = user_id);
20
21create policy "Users can delete own wish list items"
22 on public.wish_list_items for delete
23 using (auth.uid() = user_id);
24
25create index wish_list_user_idx
26 on public.wish_list_items (user_id, added_at desc);
27
28-- Share link table
29create table public.shared_wish_lists (
30 id uuid primary key default gen_random_uuid(),
31 user_id uuid references auth.users(id) on delete cascade not null unique,
32 share_token uuid not null default gen_random_uuid(),
33 is_public boolean not null default true,
34 created_at timestamptz not null default now()
35);
36
37alter table public.shared_wish_lists enable row level security;
38
39create policy "Users manage own share settings"
40 on public.shared_wish_lists for all
41 using (auth.uid() = user_id)
42 with check (auth.uid() = user_id);
43
44-- Allow public read on wish_list_items via share token (join in Edge Function)
45create policy "Public share read on wish list"
46 on public.wish_list_items for select
47 using (
48 exists (
49 select 1 from public.shared_wish_lists s
50 where s.user_id = wish_list_items.user_id
51 and s.is_public = true
52 )
53 );

Heads up: The UNIQUE constraint on (user_id, product_id) is the safety net for rapid double-taps. The last_notified_at column on wish_list_items drives price-drop notification deduplication — only notify when the price drops further than the last notified price.

Build it — pick your path

Each tool fits this feature differently. Switch tabs to compare fit, time, the exact steps, and a copy-paste prompt.

Full-stack, non-technical friendlyFit for this feature:

Easiest full path: Lovable Cloud provisions the Supabase schema and auth, generates the toggle button and list page in one session, and Supabase Realtime keeps heart state in sync across tabs.

Step by step

  1. 1Create a new Lovable project and connect Lovable Cloud so Supabase auth and the database are provisioned automatically
  2. 2Paste the prompt below and let Agent Mode build the heart toggle component, wish list page, and guest localStorage merge on auth
  3. 3Open the Cloud tab and confirm the wish_list_items table and its RLS policies were created
  4. 4Click Publish and test the share link on a separate incognito browser window to verify the public read policy works
Paste into Lovable
Build a wish list feature for an e-commerce app. Use Supabase for persistence. Heart toggle button: shadcn/ui Button with Heart icon (lucide-react); on click perform an optimistic UI update (fill the heart immediately) then insert or delete a row in the wish_list_items table (user_id, product_id, saved_price_cents, added_at). Add a UNIQUE(user_id, product_id) constraint and handle the 23505 unique violation error gracefully by treating it as 'already saved'. For logged-out users, store wish list in localStorage as an array of product_id strings; on auth state change (Supabase onAuthStateChange), batch-insert the localStorage items into wish_list_items and clear localStorage. Wish list page: product grid of saved items (join wish_list_items with products); empty state with 'Start exploring' CTA; sort by date added (default) and price; individual remove button; a 'Move to cart' button per item. Nav icon: show a badge with count of saved items. Share link: create a shared_wish_lists table with a share_token UUID; public Edge Function GET /wish-list/share/[token] returns name, price, image_url only — never user_id. Load the user's existing wish list on page mount and initialise heart state from the result set.

Where this path bites

  • Price-drop notification cron must be scheduled manually in the Supabase Dashboard after Lovable generates the Edge Function body
  • The share link public RLS policy may need manual review — confirm it does not expose user_id or email in the Edge Function response
  • Supabase Realtime for multi-tab heart sync works reliably on the published URL; test there rather than the in-editor preview

Third-party services you'll need

Wish list persistence runs entirely on Supabase. Only price-drop notification emails require a third-party service:

ServiceWhat it doesFree tierPaid from
SupabaseDatabase (wish_list_items, shared_wish_lists), Auth, RLS, Realtime for multi-tab heart sync, Edge Functions for share link and price-drop cronFree (2 projects, 500MB DB)Pro $25/mo (approx)
ResendTransactional price-drop notification emails — triggered by the scheduled Edge Function when product prices decrease100 emails/day free$20/mo (50K emails) (approx)

Swipe the table sideways to see pricing.

What it costs to run

Drag through the tiers to see how your monthly bill scales with users — no surprises later.

Estimated monthly running cost

$0/mo

Supabase free tier covers DB, auth, and Edge Functions easily. Price-drop emails within Resend free tier (100/day). Minimal DB reads.

Estimates use verified 2026 pricing and assume typical usage per user. Your real bill depends on activity, storage, and third-party plans.

What breaks when AI tools build this

The failures people actually hit on their first build — the symptom, why it happens, and the exact fix.

Duplicate rows from rapid double-taps

Symptom: Without a UNIQUE constraint, a user double-tapping the heart button inserts two rows for the same product before the first DB response arrives. The list then shows the item twice and the second remove tap leaves a phantom row.

Fix: Add UNIQUE(user_id, product_id) to wish_list_items in your SQL schema. In the toggle handler, catch PostgreSQL error code 23505 and treat it as 'already saved' rather than an error — update heart state to filled and move on.

Guest wish list disappears on login

Symptom: AI tools save wish list to localStorage for logged-out users but don't wire the Supabase onAuthStateChange listener that merges those items into the database after sign-in. The user spends time saving items, creates an account, and their list is gone.

Fix: In your prompt, explicitly require: 'On onAuthStateChange event when user transitions from null to signed-in, read localStorage wish list, batch-insert all items into wish_list_items via Supabase, then clear localStorage.' Test this flow in a fresh incognito session.

Share link exposes user_id and email

Symptom: AI-generated public share endpoints often return the full wish_list_items row including user_id, or join with auth.users to fetch display names — leaking account data to anyone with the share URL.

Fix: The share Edge Function or API route must return only product_id, name, price_cents, and image_url. Never include user_id, email, or any internal identifier in the public response. Explicitly state this in your prompt.

Heart icon shows empty on page refresh

Symptom: AI builds often initialise the heart icon from React component state (default: empty) and don't fetch the user's existing wish list on page load. After any navigation or refresh, all hearts appear empty even for saved items.

Fix: Fetch the complete list of product_ids in the user's wish_list_items on page mount (or as a server component data fetch), then pass that set to every heart button so it initialises correctly. A loading skeleton during fetch prevents the flash of wrong state.

Price-drop notification emails sent on every cron run

Symptom: The price-drop cron finds items where products.price_cents < wish_list_items.saved_price_cents and emails the user. Without deduplication, every subsequent cron run re-sends the same notification because the price is still lower than the saved price.

Fix: After sending a notification, update last_notified_at and also update saved_price_cents to the new (lower) price. Future cron runs will only trigger again if the price drops further below the newly recorded price.

Best practices

1

Always use optimistic UI for the heart toggle — any visible loading delay on a save action destroys the 'effortless save' feel that makes wish lists sticky

2

Set the UNIQUE constraint at the database level, not just in application code — double-tap races and concurrent sessions both bypass app-level guards

3

Persist guest wish list to localStorage immediately on every toggle, not just on page unload — mobile browsers kill tabs without firing unload events

4

Keep the public share endpoint response to product fields only — treat it like a public API and never include any user identity data

5

Deduplicate price-drop notifications by updating last_notified_at and saved_price_cents atomically after each notification send

6

Show the wish list item count badge in the nav immediately after any toggle (update in React context or App State) — users rely on the badge as confirmation their save worked

7

Include an 'Out of stock' indicator on wish list items rather than silently hiding them — users return specifically to buy items they saved

When You Need Custom Development

A single save-for-later list is well within what Lovable or V0 can build in an afternoon. Move to custom development when the wish list becomes a product in its own right:

  • Multi-list management — users can create named lists ('Birthday', 'Holiday', 'Home Reno') and move items between them
  • Gift registry with 'claimed by' tracking — friends and family mark items as purchased so duplicates are avoided, requiring a separate claims table and sharing UX
  • Merchant analytics dashboard aggregating wish list saves by product, conversion rate from wish list to purchase, and price sensitivity signals
  • Integration with email marketing platforms (Klaviyo, Brevo) for automated abandoned-wish-list sequences triggered by items older than 30 days with no purchase

RapidDev builds these features for production

Real auth, database, and payments — fixed price, delivered in weeks. You own 100% of the code.

Get a fixed-price quote

Frequently asked questions

Can I save wish list items for logged-out users?

Yes. Store wish list as an array of product IDs in localStorage (or a cookie) for guests. When the user signs in, read those items and batch-insert them into your Supabase wish_list_items table via the Supabase onAuthStateChange event, then clear localStorage. Explicitly include this merge step in your AI prompt — it's the most commonly skipped piece.

How do I let users share their wish list with friends?

Create a shared_wish_lists table with a UUID share_token per user. Your public share route (/wish-list/share/[token]) fetches items for that token and returns only product name, price, and image — never user_id or email. The recipient gets a read-only browseable list without needing to log in. Set a public Supabase RLS policy that allows SELECT on wish_list_items when a matching share_token exists.

How do I send price drop alerts for wish listed items?

Add a saved_price_cents column to wish_list_items to capture the price at save time. A scheduled Supabase Edge Function (daily or hourly cron, configured in the Supabase Dashboard) compares that against products.price_cents. When a decrease is detected, send an email via Resend with a direct link to the product. Store last_notified_at and update saved_price_cents after each notification to prevent repeat emails.

How do I prevent the same item being added to the wish list twice?

Add a UNIQUE(user_id, product_id) constraint to the wish_list_items table. When the insert triggers a 23505 unique violation, catch it and treat it as success (heart is already filled). Never rely solely on application-level duplicate checks — rapid taps and concurrent sessions bypass them.

Can users have multiple wish lists?

Not with the schema above — it's designed for one list per user. For multiple named lists, add a wish_lists table (id, user_id, name) and relate wish_list_items to wish_list_id instead of directly to user_id. This is a meaningful schema change and the most common custom-development trigger for this feature.

Can wish list items be moved directly to the cart?

Yes — add a 'Move to cart' button on each wish list item. On click, insert the product into your cart table (or update cart state), then optionally remove it from wish_list_items. Include 'Move to cart' button per item on the wish list page in your prompt so the AI tool generates both actions.

How do I show how many other users have wishlisted a product?

Add a wishlist_count integer column to your products table (or compute it with a COUNT query). Update it via a Supabase database trigger or increment it in your insert/delete Server Action. Display it on the product page as 'Saved by 142 people'. A DB trigger is the most reliable approach — it survives concurrent inserts that would race an application-side count.

How do I handle wish-listed items that go out of stock?

Add an in_stock boolean or stock_quantity column to your products table. On the wish list page, join against products and render an 'Out of stock' badge on affected items — and optionally send a 'Back in stock' email when inventory is replenished. Never silently hide out-of-stock wish list items; users return specifically to buy things they saved.

RapidDev

Need this feature production-ready?

RapidDev builds a wish list into real apps — auth, database, payments — at $13K–$25K.

Book a free consultation

30-min call. No commitment.

Want this built for you?

We ship production apps at a fixed price — $13K–$25K, 6–10 weeks, source code yours. You've seen what it takes; we do it every week.

Get a fixed-price quote

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.