@godscodes/medusa-wishlist-plugin
v0.4.4
Published
Plugin for wishlist functionality in Medusa.
Maintainers
Readme
Medusa v2 Example: Wishlist Plugin
This directory holds the code for the Wishlist Plugin Guide.
You can either:
- install and use it as a plugin in the Medusa application;
- or copy its source files into an existing Medusa application, without using them as a plugin.
Prerequisites
Installation
- In your Medusa application, run the following command to install the wishlist plugin:
yarn add @godscodes/medusa-wishlist-plugin- Add the plugin to the
pluginsarray inmedusa-config.ts:
module.exports = defineConfig({
// ...
plugins: [
{
resolve: "@godscodes/medusa-wishlist-plugin",
options: {}
}
]
})- Add the following
adminconfiguration inmedusa-config.ts:
module.exports = defineConfig({
// ...
admin: {
vite: () => {
return {
optimizeDeps: {
include: ["qs"],
},
};
},
},
})
- Run the
db:migratecommand to run migrations and sync links:
npx medusa db:migrateCopy 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.
- Copy the content of the following directories:
src/api/storeandsrc/api/middlewares.tssrc/linksrc/modules/wishlistsrc/workflows
- Add the Wishlist Module to
medusa-config.ts:
module.exports = defineConfig({
// ...
modules: [
{
resolve: "./src/modules/wishlist"
},
]
})- Run the
db:migratecommand to run migrations and sync links:
npx medusa db:migrateTest 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 Documentatin
- OpenAPI Spec file: Can be imported into tools like Postman to view and send requests to this project's API routes.
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 @godscodes/medusa-wishlist-plugin.
✅ Prerequisites
Before starting, ensure you have:
- Medusa backend (
v2.8.6) installed and running. - The plugin
@godscodes/medusa-wishlist-plugininstalled and configured. - Next.js Starter from Medusa: https://docs.medusajs.com/resources/nextjs-starter
- Next.js ≥ 15
- Node.js ≥ 17
@tanstack/react-queryinstalled in your Next.js frontend.
Install React Query:
npm install @tanstack/react-query
# or
yarn add @tanstack/react-query1. Create React Query Client
File: lib/react-query-client.ts
import { QueryClient } from "@tanstack/react-query"
export const queryClient = new QueryClient()2. Setup Query Provider
File: app/providers.tsx
"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 Providers in app/layout.tsx
import Providers from "./providers"
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}4. Implement Wishlist Logic
File: lib/data/wishlist.ts
"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: src/modules/products/components/product-actions/index.tsx
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: app/[countryCode]/(main)/wishlist/page.tsx
"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 Wishlist7. Add Wishlist to Menu
File: src/modules/layout/components/side-menu/index.tsx
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
@tanstack/react-query.
