npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@godscodes/medusa-wishlist-plugin

v0.4.4

Published

Plugin for wishlist functionality in Medusa.

Readme

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 plugins array in medusa-config.ts:
module.exports = defineConfig({
  // ...
  plugins: [
    {
      resolve: "@godscodes/medusa-wishlist-plugin",
      options: {}
    }
  ]
})
  1. Add the following admin configuration in medusa-config.ts:
module.exports = defineConfig({
  // ...
  admin: {
    vite: () => {
      return {
        optimizeDeps: {
          include: ["qs"],
        },
      };
    },
  },
})
  1. Run the db:migrate 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:
  • src/api/store and src/api/middlewares.ts
  • src/link
  • src/modules/wishlist
  • src/workflows
  1. Add the Wishlist Module to medusa-config.ts:
module.exports = defineConfig({
  // ...
  modules: [
    {
      resolve: "./src/modules/wishlist"
    },
  ]
})
  1. Run the db:migrate 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 @godscodes/medusa-wishlist-plugin.

✅ Prerequisites

Before starting, ensure you have:

  • Medusa backend (v2.8.6) installed and running.
  • The plugin @godscodes/medusa-wishlist-plugin installed and configured.
  • Next.js Starter from Medusa: https://docs.medusajs.com/resources/nextjs-starter
  • Next.js ≥ 15
  • Node.js ≥ 17
  • @tanstack/react-query 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: 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 Wishlist

7. 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.