AstroBaaS

Storefront & headless

Wishlist / Save for Later

Free — GPL coresize Mplanned, not built

Generated from docs/plan/core/wishlist-save-for-later/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.

A wishlist and save-for-later feature letting customers bookmark products for later. Supports logged-in and guest wishlists (guest uses localStorage). Includes API, wishlist page, quick-add buttons, shareable URLs, and admin viewing. Feeds retargeting and email campaigns.

The problem

Customers can’t save products to revisit later; merchants lose abandoned-browsing data for retargeting emails. Customers either leave and forget, or add items to cart and abandon it (polluting cart metrics). Merchants need a signal (‘customer saved this’) to trigger ‘Your saved item is now on sale’ campaigns.

What it does

  • Backend wishlist storage (database table: customer_id, product_id, created_at, updated_at, optional notes)
  • Guest wishlist using browser localStorage (persists across sessions; no server storage needed)
  • Login-triggered wishlist merge (guest wishlist is merged into customer account on sign-in; no dupes)
  • REST API: POST /api/wishlist (add), DELETE /api/wishlist/:productId (remove), GET /api/wishlist (list)
  • Wishlist page component (Next.js route) displaying all saved items with current prices
  • Wishlist item card (product image, title, price, ‘View Details’ link, remove button)
  • Price-change indicator (if product price dropped since add, show old and new price side-by-side)
  • Wishlist share URL (shareable link to public wishlist; optional expiry, public read-only access)
  • Email trigger: merchant can email customer their wishlist with current prices and links
  • Webhook events (wishlist_item_added, wishlist_item_removed, wishlist_shared) with customer_id and product_id
  • Admin UI: view customer wishlists, see top 10 saved products (most-wishlisted items)

What it deliberately does NOT do

Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.

  • Price tracking and automated alerts — requires price-history tables and scheduler; separate paid feature for retention campaigns
  • Social sharing widgets (Facebook/Pinterest buttons) — social integration is merchant’s responsibility, not core
  • Wishlist organization (lists, categories, tags) — single flat wishlist only; organizing is a paid personalization layer
  • Analytics dashboard (view counts, conversion rates, saved→purchased %) — analytics is paid add-on; core stores data only
  • Mobile app wishlist sync — mobile is merchant’s; REST API is consistent, so apps work if merchant builds them
  • Automatic wishlist expiry or purges — merchant deletes manually or via admin; retention policies are merchant-specific

Data model

Migration required. New tables: wishlist_items (id, customer_id, product_id, price_at_add INT, created_at, updated_at), wishlist_shares (id, customer_id, token STRING, expires_at NULLABLE, accessed_count INT). Customers table: add wishlist_token nullable STRING. Settings: wishlist_enabled BOOLEAN default false, wishlist_max_items INT default 100.

API

  • POST /api/wishlist {product_id: string} — add product to wishlist (auth or guest)
  • DELETE /api/wishlist/:productId — remove product from wishlist
  • GET /api/wishlist — list customer’s wishlist items with current prices
  • POST /api/wishlist/share {expires_in_days: int} — create shareable wishlist link, return token
  • GET /api/wishlist/share/:token — view shared wishlist (public, no auth)
  • GET /api/wishlist/stats — admin endpoint, return top 10 most-wishlisted products

Every route added here must also appear in src/pages/openapi.json.ts — a test fails the build if it does not.

Admin

Admin page showing all wishlist items per customer, filterable by product_id. Setting to enable/disable wishlist feature. Setting for max items per wishlist (default 100). Webhook test form to validate merchant’s event URL. Top 10 saved products card showing product title and save count.

The seam — why this is core

Core owns wishlist storage, API endpoints, guest/login merge logic, localStorage integration, and basic URL sharing. Paid pack owns price-tracking alerts, email automation, analytics dashboard, and retention campaign triggers.

Core owns the interface + honest wishlist storage; customer engagement is infrastructure, not a per-country obligation or credential.

Dependencies

  • Authentication system (core; identifies customers on sign-in for merge)
  • Product API (core; fetches product data for wishlist items)
  • Webhook system (core; emits wishlist_item_* events)
  • Email layer (core; merchant templates emails; core sends via configured SMTP)
  • Settings system (core; toggles feature on/off)

Acceptance checks

Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.

  • Logged-in customer POSTs to /api/wishlist with product_id; item appears in GET /api/wishlist response
  • Guest user adds product to wishlist; it’s stored in localStorage as JSON array; survives page reload
  • Guest logs in; wishlist merges (guest items + account items, deduplicated by product_id)
  • Customer DELETEs product from wishlist; item vanishes from GET /api/wishlist
  • Merchant generates shareable wishlist URL (POST /api/wishlist/share); returns public token; GET /api/wishlist/share/:token shows products (no auth required)
  • Admin sees top 10 saved products in /admin/wishlist/stats (e.g., ‘Product X: 234 saves’)
  • Wishlist disabled: POST /api/wishlist returns 404 and feature hidden from storefront
  • Webhook fires on add/remove with payload {event: ‘wishlist_item_added’, customer_id, product_id, timestamp}
  • price_at_add is stored when product is added; if price changes, wishlist shows old and new price

Risks

Guest merge collision: if same product in guest and account wishlists, store once (dedup by product_id, don’t double-count). Price snapshot: if product price changes between add and viewing, price-change detection fails (must store price_at_add as integer). Share token expiry: if merchant loses token, shared URL breaks (make expiry configurable; default never). Webhook payload size: if customer has 1000 wishlisted items, webhook payload is huge (send summary, not full item list). localStorage quota: if customer adds 100+ items, localStorage fills up (enforce max_items setting on client).

Commercial context

Suggested pricefree (core)
Rival anchorShopify: included (via apps, ~$20/mo); Magento: included

The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.


Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full catalogue and ../../AI-GUIDE.md for how to work on this repository.