Medusa plugins
W

Wishlist plugin

Plugin for wishlist functionality in Medusa.

Need customizations for this plugin?

Get in touch
npm install @godscodes/medusa-wishlist-plugin
Category
other
Built by
godscodes
Type
unknown
Last updated
2 months ago
Monthly downloads
53

Medusa v2 Example: Wishlist Plugin

This directory holds the code for the Wishlist Plugin Guide.

You can either:

Prerequisites

Installation

  1. In your Medusa application, run the following command to install the wishlist plugin:
yarn add @godscodes/medusa-wishlist-plugin
  1. Add the plugin to the array in :
module.exports = defineConfig({
// ...
plugins: [
{
resolve: "@godscodes/medusa-wishlist-plugin",
options: {}
}
]
})
  1. Add the following configuration in :
module.exports = defineConfig({
// ...
admin: {
vite: () => {
return {
optimizeDeps: {
include: ["qs"],
},
};
},
},
})
  1. Run the command to run migrations and sync links:
npx medusa db:migrate

Copy into Existing Medusa Application

You can also copy the source files into an existing Medusa application, which will add them not as a plugin, but as standard Medusa customizations.

  1. Copy the content of the following directories:
  • and
  1. Add the Wishlist Module to :
module.exports = defineConfig({
// ...
modules: [
{
resolve: "./src/modules/wishlist"
},
]
})
  1. Run the command to run migrations and sync links:
npx medusa db:migrate

Test it Out

To test out that the plugin is working, you can go to any product page on the Medusa Admin and see a Wishlist section at the top of the page. You can also try importing the OpenAPI Spec file and using the API routes added by this plugin.

More Resources


Medusa Wishlist Integration (Next.js Starter)

This guide walks you through integrating a Wishlist feature in the Medusa Next.js Starter, using Medusa v2.8.6 and the .

✅ Prerequisites

Before starting, ensure you have:

  • Medusa backend () installed and running.
  • The plugin installed and configured.
  • Next.js Starter from Medusa: https://docs.medusajs.com/resources/nextjs-starter
  • Next.js ≥ 15
  • Node.js ≥ 17
  • installed in your Next.js frontend.

Install React Query:

npm install @tanstack/react-query
# or
yarn add @tanstack/react-query

1. Create React Query Client

File:

import { QueryClient } from "@tanstack/react-query"
export const queryClient = new QueryClient()

2. Setup Query Provider

File:

"use client"
import { QueryClientProvider } from "@tanstack/react-query"
import { queryClient } from "@lib/react-query-client"
export default function Providers({ children }: { children: React.ReactNode }) {
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
}

3. Wrap in

import Providers from "./providers"
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}

4. Implement Wishlist Logic

File:

"use server"
import { sdk } from "@lib/config"
import { getAuthHeaders } from "@lib/data/cookies"
import medusaError from "@lib/util/medusa-error"
import { revalidateTag } from "next/cache"
type WishlistItem = {
id: string
product_variant_id: string
wishlist_id: string
created_at: string
updated_at: string
product_variant: {
id: string
title: string
sku: string
product_id: string
product: {
id: string
images?: { url: string }[]
}
}
}
type Wishlist = {
id: string
customer_id: string
sales_channel_id: string
created_at: string
updated_at: string
items: WishlistItem[]
}
type WishlistResponse = { wishlist: Wishlist }
export async function getWishlist(): Promise<Wishlist | null> {
try {
const result = await sdk.client.fetch("/store/customers/me/wishlists", {
method: "GET",
headers: await getAuthHeaders(),
})
if (typeof result === "object" && result !== null && "wishlist" in result) {
return (result as WishlistResponse).wishlist
}
return null
} catch (err: any) {
if (err?.response?.status === 404 || err?.message?.toLowerCase().includes("no wishlist")) {
return null
}
medusaError(err)
return null
}
}
export async function createWishlist(): Promise<Wishlist> {
try {
const res = await sdk.client.fetch("/store/customers/me/wishlists", {
method: "POST",
headers: await getAuthHeaders(),
})
if (typeof res === "object" && res !== null && "wishlist" in res) {
return (res as WishlistResponse).wishlist
}
throw new Error("Failed to create wishlist")
} catch (err) {
medusaError(err)
throw err
}
}
export async function addToWishlist({ variantId }: { variantId: string }) {
if (!variantId) throw new Error("Missing variant ID")
try {
let wishlist = await getWishlist()
if (!wishlist) wishlist = await createWishlist()
await sdk.client.fetch("/store/customers/me/wishlists/items", {
method: "POST",
body: { variant_id: variantId },
headers: await getAuthHeaders(),
})
await revalidateTag("wishlist")
} catch (err) {
medusaError(err)
throw err
}
}
export async function removeFromWishlist(itemId: string): Promise<void> {
if (!itemId) throw new Error("Missing wishlist item ID")
try {
await sdk.client.fetch(`/store/customers/me/wishlists/items/${itemId}`, {
method: "DELETE",
headers: await getAuthHeaders(),
})
await revalidateTag("wishlist")
} catch (err) {
medusaError(err)
throw err
}
}
export async function isVariantInWishlist(variantId: string): Promise<boolean> {
const wishlist = await getWishlist()
return wishlist?.items?.some((item) => item.product_variant_id === variantId) ?? false
}

5. Add UI: Product Page Integration

File:

import { useMutation, useQueryClient } from "@tanstack/react-query"
import { addToWishlist, isVariantInWishlist } from "@lib/data/wishlist"
const [isAdding, setIsAdding] = useState(false)
const [alreadyInWishlist, setAlreadyInWishlist] = useState(false)
const queryClient = useQueryClient()
const { mutateAsync: addToWishlistMutate, isPending: isWishlistPending } = useMutation({
mutationKey: ["wishlist-add-item"],
mutationFn: async ({ variantId }: { variantId: string }) => {
return await addToWishlist({ variantId })
},
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["wishlist"] })
},
})
const handleAddToWishlist = async () => {
if (!selectedVariant?.id) return
await addToWishlistMutate({ variantId: selectedVariant.id })
setAlreadyInWishlist(true)
}
useEffect(() => {
const checkWishlist = async () => {
if (selectedVariant?.id) {
const exists = await isVariantInWishlist(selectedVariant.id)
setAlreadyInWishlist(exists)
}
}
checkWishlist()
}, [selectedVariant?.id])
<Button
onClick={handleAddToWishlist}
disabled={!selectedVariant || !!disabled || alreadyInWishlist}
isLoading={isWishlistPending}
variant="primary"
className="w-full h-10"
>
{alreadyInWishlist ? "Added to Wishlist" : "Add to Wishlist"}
</Button>

6. Display Wishlist Page

File:

"use client"
import { useQuery } from "@tanstack/react-query"
import { getWishlist, removeFromWishlist } from "@lib/data/wishlist"
import Link from "next/link"
import { useState } from "react"
import { queryClient } from "@lib/react-query-client"
type WishlistItem = { id: string; product_variant: { id: string; title: string; sku: string; product: { id: string; images?: { url: string }[] } } }
type Wishlist = { items: WishlistItem[] }
const Wishlist = () => {
const { data: wishlist, isLoading } = useQuery<Wishlist | null>({ queryKey: ["wishlist"], queryFn: getWishlist })
const [removingId, setRemovingId] = useState<string | null>(null)
const handleRemove = async (itemId: string) => {
setRemovingId(itemId)
await removeFromWishlist(itemId)
await queryClient.invalidateQueries({ queryKey: ["wishlist"] })
setRemovingId(null)
}
if (isLoading) return <div className="text-center">Loading your wishlist...</div>
if (!wishlist || wishlist.items.length === 0)
return <div className="text-center mt-24"><h2>No items in wishlist</h2></div>
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mt-10">
{wishlist.items.map((item) => {
const product = item.product_variant.product
const imageUrl = product?.images?.[0]?.url || "/placeholder.jpg"
return (
<div key={item.id} className="bg-white p-4 rounded shadow">
<img src={imageUrl} alt={item.product_variant.title} className="w-full h-48 object-cover" />
<h3 className="mt-2 font-semibold">{item.product_variant.title}</h3>
<button
onClick={() => handleRemove(item.id)}
disabled={removingId === item.id}
className="mt-3 text-sm text-red-600 border px-4 py-1 rounded hover:bg-red-100"
>
{removingId === item.id ? "Removing..." : "Remove"}
</button>
</div>
)
})}
</div>
)
}
export default Wishlist

7. Add Wishlist to Menu

File:

const SideMenuItems = {
Home: "/",
Store: "/store",
Account: "/account",
Cart: "/cart",
Wishlist: "/wishlist",
}

8. Start the Development Server

yarn dev

✅ Done!

You've successfully added a full-featured wishlist to your Medusa storefront 🎉


📌 Notes

  • Make sure the Medusa wishlist plugin is enabled in your backend.
  • Wishlist will auto-create when a user adds their first item.
  • UI updates using .