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

medusa-product-helper

v0.0.55

Published

A starter for Medusa plugins.

Downloads

646

Readme

Overview

medusa-product-helper extends Medusa v2 with:

  • Dynamic product filtering – Store and admin product list APIs with a provider-based filter system. Filter by category, collection, price range, metadata, variant options, promotions, best sellers, rating, availability, scalar product fields, variant SKU/barcode, and more.
  • Scalar product filters – Filter directly on product model fields that Medusa's built-in API does not expose: discountable, material, origin_country, hs_code, weight, and variant-level fields sku, barcode, ean, upc.
  • Centralized filter registry – All filter Zod schemas live in src/utils/product-filter-registry.ts. Adding a new filterable field means editing one file; validators derive from it automatically.
  • Filter descriptor format – Every filter supports either legacy query shapes or a structured descriptor { type, action, value }. Metadata supports per-key descriptors with actions like exact_match, contains, in, starts_with, ends_with, not_equals.
  • Wishlist – Customer wishlist (store API), admin wishlist stats, and a Wishlist performance widget in the product details sidebar.
  • Inventory CSV Import (Admin) - Adds an Import button on /app/inventory and imports exported inventory CSV rows to update existing inventory levels.
  • Custom filter providers – Register your own filter logic without changing plugin code.

Compatibility

This plugin is compatible with versions >= 2.4.0 of @medusajs/medusa.

Installation

yarn add medusa-product-helper
# or
npm install medusa-product-helper

Configuration

Add the plugin to your medusa-config.ts:

import type { ConfigModule } from "@medusajs/framework/types"

const plugins = [
  {
    resolve: "medusa-product-helper",
    options: {
      // Default price range when none is specified
      default_price_range: {
        label: "custom",
        min: null,
        max: null,
      },
      // Promotion window configuration
      promotion_window: {
        // Product metadata field key that stores the promotion start date
        start_metadata_key: "promotion_start",
        // Product metadata field key that stores the promotion end date
        end_metadata_key: "promotion_end",
        // If true, promotions without an end date are considered active
        treat_open_ended_as_active: true,
      },
      // Product availability filtering options
      availability: {
        // Only show products that are in stock
        require_in_stock: false,
        // Include products available for preorder
        include_preorder: true,
        // Include products available for backorder
        include_backorder: true,
        // Include gift cards in product listings
        include_gift_cards: false,
        // Only show publishable products
        publishable_only: true,
      },
      // Product rating configuration
      rating: {
        // Enable rating-based filtering
        enabled: true,
        // Minimum rating value (0-5)
        min: 0,
        // Maximum rating value (0-5)
        max: 5,
        // Require products to have at least one review
        require_reviews: false,
      },
      // Variant option filtering configuration
      variantOption: {
        // Enable variant option filtering
        enabled: true,
        // Case-sensitive option value matching
        caseSensitive: false,
        // Maximum number of variant filters allowed per request (optional)
        maxFilters: 10,
      },
      // Custom filter providers (optional)
      filterProviders: [
        // File path (relative to project root)
        "./src/providers/margin-provider.ts",
        // Or module reference
        "@my-org/custom-filters/brand-provider",
      ],
      // Disable specific built-in providers (optional)
      disableBuiltInProviders: [
        // "rating", // Uncomment to disable rating filter provider
      ],
    },
  },
]

export default {
  projectConfig: {
    // Your Medusa project configuration
    // database_url: process.env.DATABASE_URL,
    // ...
  },
  plugins,
} satisfies ConfigModule

Configuration Options

Wishlist Insights Widget

The admin extension also injects a Wishlist performance card into the product details sidebar (product.details.side.after). The widget:

  • Shows how many unique customers saved the current product.
  • Highlights whether the product ranks within the most wishlisted items.
  • Displays a live leaderboard (top 5) powered by /admin/wishlist/stats.

No additional configuration is required—install the plugin, run the wishlist migration, and open any product inside Medusa Admin to see the UI.

Customer Details Carts card

The admin extension injects a Carts card on the Customer Details page (Customers → [customer name]). The card lists cart line items (variant ID, product, quantity, unit price) from that customer’s active carts (in store, not yet ordered or checked out). The list API returns line items when called with customer_id. It is registered for:

  • customer.details.side.after – right column, after the Addresses section
  • customer.details.after – main column, after Orders and Customer Groups

If the Carts card does not appear:

  1. Register the plugin in your main Medusa app medusa-config.ts: add "medusa-product-helper" (or { resolve: "medusa-product-helper", options: {} }) to the plugins array.
  2. Rebuild the plugin (in the plugin repo): run npx medusa plugin:build.
  3. Restart or rebuild the main app: run npm run dev (or npx medusa develop) so the admin loads the plugin’s admin bundle; if you use a production build, run npx medusa build and restart.
  4. Open Customers → any customer; the Carts card should appear in the right column (after Addresses) and/or in the main area.

Inventory CSV Import button

The admin extension injects an Import button on the inventory list page (/app/inventory) beside the default Create action (zone: inventory_item.list.before).

The button uploads a CSV file to:

  • POST /admin/product-helper/inventory/import

CSV requirements (matches inventory export shape: id, title, location, instock):

  • Required headers: id, location, instock
  • Optional column: title (ignored during import; column may be omitted)
  • id must be an existing inventory item ID (the iitem_… value from export). The admin SKU column is not used for import matching.
  • location may be blank on a row: the importer uses the last non-empty location above in the file (spreadsheet-style carry-down), or if the cell is blank and the item has exactly one existing stock level, that level’s location is used
  • When location is set, it must match an existing stock location (location ID or exact location name, case-insensitive)
  • instock must be a non-negative integer

Behavior:

  • Update-only: each row must match an existing inventory level for (inventory item id from export, stock location). Only updateInventoryLevels is used; nothing is created.
  • Export-compatible workflow: use the CSV from GET /admin/export/inventory-item (same columns), edit instock (and optionally title—ignored). Do not add new (id, location) pairs that were not already present in the export unless you have first added that item to that location in Admin.
  • Atomic validation: if any row fails validation, no rows are updated.

Successful response example:

{
  "message": "Inventory import completed",
  "total_rows": 7,
  "updated_rows": 7
}

Promotion Window

Configure how promotion dates are tracked using product metadata fields:

  • start_metadata_key: Product metadata field key storing promotion start date
  • end_metadata_key: Product metadata field key storing promotion end date
  • treat_open_ended_as_active: If true, promotions without an end date are considered active

Note: This feature uses product metadata fields for storage. For metadata management UI and configuration, use medusa-dynamic-metadata.

Availability

Control which products appear in listings:

  • require_in_stock: Only show in-stock products
  • include_preorder: Include preorder products
  • include_backorder: Include backorder products
  • include_gift_cards: Include gift cards
  • publishable_only: Only show publishable products

Rating

Configure rating-based filtering:

  • enabled: Enable rating filtering
  • min: Minimum rating (0-5)
  • max: Maximum rating (0-5)
  • require_reviews: Require at least one review

Best Seller

Configure best seller filtering and sorting:

  • enabled: Enable best seller filtering (default: true)
  • default_timeframe_days: Optional default timeframe in days for best seller queries
  • min_orders_threshold: Minimum order count threshold to be considered a best seller (default: 1)

Variant Option

Configure variant option filtering:

  • enabled: Enable variant option filtering (default: true)
  • caseSensitive: Enable case-sensitive option value matching (default: false)
  • maxFilters: Maximum number of variant filters allowed per request (optional)

Filter Providers

Configure custom filter providers:

  • filterProviders: Array of file paths or module references to custom filter providers
  • disableBuiltInProviders: Array of provider identifiers to disable (e.g., ["rating"])

See the Provider Development Guide for details on creating custom filter providers.

Dynamic Filter Provider System

The plugin includes a provider-based dynamic filter system that allows you to extend product filtering capabilities without modifying the plugin code. All built-in filters (category, collection, price range, etc.) are implemented as filter providers, and you can add your own custom providers.

Built-in Filter Providers

The plugin includes the following built-in filter providers:

  • Category Filter (category_id): Filter products by category IDs. When you pass one or more category IDs, the API returns products in those categories and in all their child categories (descendants at every level).
  • Collection Filter (collection_id): Filter products by collection IDs
  • Metadata Filter (metadata): Filter products by metadata with optional descriptor format (exact_match, contains, in, etc.). Multiple keys are AND. For legacy string values, commas mean OR within that key (e.g. metadata[material]=A,B matches material A or B).
  • Price Range Filter (price_range): Filter products by price range (min/max)
  • Promotion Filter (promotion): Filter products by promotion criteria (discount, type, dates)
  • Promotion Window Filter (promotion_window): Filter products by promotion date windows (metadata-based)
  • Availability Filter (availability): Filter products by availability status
  • Rating Filter (rating): Filter products by rating (min/max)
  • Best Seller Filter (best_seller): Filter and sort products by best seller status (order count)
  • Variant Option Filter (variant_option): Filter products by variant option values (e.g., color, size); OR logic across options/values
  • Option Value Filter (option_value): Filter by option value strings (e.g. option_value[]=red&option_value[]=blue or option_value[]=red,blue); product is included if any variant matches any of the values (OR)
  • Base Product Filters (base_product): Filter by ID, handle, tags, sales channel
  • Scalar Product Filters (scalar_product): Filter by direct product model fields (discountable, material, origin_country, hs_code, weight) and variant-level fields (sku, barcode, ean, upc) — all resolved at the DB query level

Using Filters

All filters are available through the /store/product-helper/products endpoint:

# Filter by category (includes products in that category and all child categories)
GET /store/product-helper/products?category_id=cat_123,cat_456

# Filter by price range
GET /store/product-helper/products?price_min=10&price_max=100

# Multiple filters
GET /store/product-helper/products?category_id=cat_123&price_min=10&rating_min=4

# Filter to show only best sellers
GET /store/product-helper/products?best_seller=true&region_id=reg_123

# Sort by best seller status
GET /store/product-helper/products?order=best_seller&region_id=reg_123

# Best sellers in last 30 days
GET /store/product-helper/products?best_seller=true&best_seller_timeframe_from=2024-01-01&region_id=reg_123

# Best sellers with minimum order count
GET /store/product-helper/products?best_seller=true&best_seller_min_orders=5&region_id=reg_123

# Filter by variant options (products with red variants)
GET /store/product-helper/products?[variant]color=red&region_id=reg_123

# Filter by multiple variant option values (OR logic)
GET /store/product-helper/products?[variant]color=red&[variant]color=blue&region_id=reg_123

# Filter by multiple variant options (OR logic - matches products with red OR large variants)
GET /store/product-helper/products?[variant]color=red&[variant]size=large&region_id=reg_123

# Combine variant filters with other filters
GET /store/product-helper/products?[variant]color=red&category_id=cat_123&price_min=10&region_id=reg_123

# Filter by option values (OR: variant matches red OR large)
GET /store/product-helper/products?option_value[]=red&option_value[]=large&region_id=reg_123

# Same OR semantics with a comma-separated array entry (e.g. Beige or Blue)
GET /store/product-helper/products?option_value[]=Beige,Blue&region_id=reg_123

# --- Scalar product & variant field filters ---

# Only discountable products
GET /store/product-helper/products?discountable=true&region_id=reg_123

# Products made of a specific material (single value)
GET /store/product-helper/products?material=cotton&region_id=reg_123

# Products made of cotton OR linen (comma-separated or repeated params)
GET /store/product-helper/products?material=cotton,linen&region_id=reg_123

# Filter by country of origin
GET /store/product-helper/products?origin_country=US&region_id=reg_123

# Filter by HS code
GET /store/product-helper/products?hs_code=6109100010&region_id=reg_123

# Filter by weight (exact match)
GET /store/product-helper/products?weight=500&region_id=reg_123

# Filter by variant SKU
GET /store/product-helper/products?sku=SKU-001&region_id=reg_123

# Multiple SKUs
GET /store/product-helper/products?sku=SKU-001,SKU-002&region_id=reg_123

# Filter by barcode
GET /store/product-helper/products?barcode=0123456789&region_id=reg_123

# Filter by EAN
GET /store/product-helper/products?ean=4006381333931&region_id=reg_123

# Filter by UPC
GET /store/product-helper/products?upc=012345678905&region_id=reg_123

# Combine scalar filters with other filters
GET /store/product-helper/products?discountable=true&material=cotton&origin_country=US&region_id=reg_123

Note: Store requests require region_id (used for currency and pricing). Admin requests do not.

Variant Option Filtering

The variant option filter allows you to filter products by variant properties like color, size, material, etc. It uses a special query parameter syntax: [variant]optionName=value.

Key Features:

  • OR Logic: Products are returned if ANY of their variants match ANY of the filter conditions
  • Case-Insensitive: Option names and values are matched case-insensitively by default (configurable)
  • Multiple Values: You can specify multiple values for the same option (e.g., red OR blue)
  • Multiple Options: You can filter by multiple different options (e.g., color OR size)

Examples:

# Products with any red variant
GET /store/product-helper/products?[variant]color=red&region_id=reg_123

# Products with red OR blue variants (multiple values)
GET /store/product-helper/products?[variant]color=red&[variant]color=blue&region_id=reg_123

# Products with red variants OR large variants (multiple options)
GET /store/product-helper/products?[variant]color=red&[variant]size=large&region_id=reg_123

# Combine with other filters
GET /store/product-helper/products?[variant]color=red&category_id=cat_shoes&price_max=100&region_id=reg_123

Admin API:

The same variant filtering is available on the admin endpoint without region requirement:

# Admin: Filter by variant options
GET /admin/product-helper/products?[variant]color=red

# Admin: Multiple variant filters
GET /admin/product-helper/products?[variant]color=red&[variant]size=large&category_id=cat_123

Scalar Product & Variant Filters

These filters are applied directly at the database query level (no post-query in-memory pass) via the ScalarProductFilterProvider (identifier: scalar_product).

Product-level scalar fields:

| Query param | Type | Description | |---|---|---| | discountable | boolean | Include (true) or exclude (false) discountable products | | material | string or string[] | Filter by product material (e.g. cotton, linen) | | origin_country | string or string[] | Filter by country of origin (e.g. US, DE) | | hs_code | string or string[] | Filter by Harmonized System tariff code | | weight | number | Filter by exact product weight |

Variant-level scalar fields (resolved as variants.field in the DB query):

| Query param | Type | Description | |---|---|---| | sku | string or string[] | Filter by variant SKU(s) | | barcode | string or string[] | Filter by variant barcode(s) | | ean | string or string[] | Filter by variant EAN(s) | | upc | string or string[] | Filter by variant UPC(s) |

All string-array fields accept either a comma-separated string (material=cotton,linen) or a repeated query param (material[]=cotton&material[]=linen). The boolean discountable field accepts true/false, 1/0, yes/no, or y/n.

Examples:

# Only non-discountable products
GET /store/product-helper/products?discountable=false&region_id=reg_123

# Products from multiple countries
GET /store/product-helper/products?origin_country=US,DE&region_id=reg_123

# Find a product by SKU (useful for cart recovery or deep-link lookup)
GET /store/product-helper/products?sku=SHIRT-RED-L&region_id=reg_123

# Combine: cotton products from US that are discountable
GET /store/product-helper/products?material=cotton&origin_country=US&discountable=true&region_id=reg_123

Admin API: All scalar filters are available on the admin endpoint without region_id:

GET /admin/product-helper/products?sku=SKU-001
GET /admin/product-helper/products?discountable=true&material=cotton

Filter descriptor format

Every filter parameter can be sent either in its legacy shape or as a descriptor object { type, action, value }. For structured filters (e.g. price_range, promotion, best_seller), the descriptor is a wrapper: value is the same object as before. For value/list filters (category_id, collection_id, option_value, variant_option), value is the list or scalar; for metadata, each key’s value can be a descriptor with full matching semantics.

Descriptor fields:

| Field | Allowed values | | -------- | ----------------- | | type | string, int, number, boolean (or object for structured filters) | | action | exact_match, contains, starts_with, ends_with, in, not_equals | | value | Primitive, array, or object (same shape as the filter’s legacy value) |

Metadata supports per-key descriptors with full matching (exact_match, contains, in, etc.). Other filters accept a single top-level descriptor and unwrap to the legacy shape; variant_option can have a descriptor per option (unwrap to string[] per key).

Examples (send descriptor as URL-encoded JSON in query):

# Store: region_id is required
BASE="https://your-store.com/store/product-helper/products?region_id=reg_01ABC"

# category_id as descriptor
curl -X GET "$BASE&category_id=%7B%22type%22%3A%22string%22%2C%22action%22%3A%22in%22%2C%22value%22%3A%5B%22cat_1%22%2C%22cat_2%22%5D%7D"

# metadata per-key descriptor (exact_match)
curl -X GET "$BASE&metadata=%7B%22color%22%3A%7B%22type%22%3A%22string%22%2C%22action%22%3A%22exact_match%22%2C%22value%22%3A%22red%22%7D%7D"

# metadata contains
# metadata = { "material": { "type": "string", "action": "contains", "value": "Cotton" } }

# price_range as descriptor (value = same object as legacy)
# price_range = { "type": "number", "action": "exact_match", "value": { "min": 10, "max": 100 } }

Admin: Same filter and descriptor support at GET /admin/product-helper/products (no region_id required).

Backward compatibility: all existing requests using legacy values (e.g. category_id=cat_1,cat_2, metadata[color]=red, [variant]color=red) continue to work unchanged.

Filter Registry — Adding New Filters

All Zod validation schemas for filterable fields live in a single centralized file:

src/utils/product-filter-registry.ts

It exports three schemas that validators import and .merge() from:

| Export | Fields | |---|---| | ProductScalarFilterSchema | discountable, material, origin_country, hs_code, weight | | ProductVariantScalarFilterSchema | sku, barcode, ean, upc | | Shared primitives | booleanish, stringArray, optionalNumberish |

To add a new scalar DB-level filter in one change:

  1. Add the field to the appropriate schema in product-filter-registry.ts.
  2. Add the field to ScalarProductFilterProvider.apply() in scalar-product-provider.ts.
  3. Add extraction in ProductFilterService.extractScalarFilters().

No changes needed to DynamicFilterService, FilterProviderRegistry, or validators.ts (the validator auto-derives from the merged schema).

Custom Filter Providers

You can create custom filter providers to add new filtering capabilities. See the Provider Development Guide for complete documentation.

Quick Example:

// src/providers/margin-provider.ts
import { BaseFilterProvider } from "medusa-product-helper/providers/filter-providers"

export class MarginFilterProvider extends BaseFilterProvider {
  static readonly identifier = "margin"
  static readonly displayName = "Margin Filter"
  
  apply(filters: Record<string, unknown>, value: unknown): Record<string, unknown> {
    if (!value || typeof value !== "object") return filters
    
    const { min, max } = value as { min?: number; max?: number }
    // Apply margin filter logic...
    return filters
  }
}

Then register it in medusa-config.ts:

{
  resolve: "medusa-product-helper",
  options: {
    filterProviders: ["./src/providers/margin-provider.ts"],
  },
}

Wishlist Feature

The plugin includes a comprehensive wishlist feature that allows customers to save products they're interested in and admins to view wishlist statistics.

Security & Access Control

  • Store-facing wishlist routes authenticate strictly as customers. Customer IDs are always derived from the session or JWT auth context and never taken from the request payload, so an admin or API client cannot spoof a customer identifier.
  • Admin-facing wishlist statistics are available only to admin actors (user sessions or secret API keys). Customer tokens receive a 403 response when attempting to access /admin/wishlist/stats.
  • This separation ensures only customers can manage their wishlist entries while only admins can inspect aggregate wishlist state.

Module Registration

The wishlist module is automatically registered when you add the plugin to your Medusa configuration. No additional configuration is required for basic usage.

Database Migration

After installing the plugin, generate and run the database migration:

npx medusa plugin:db:generate
npx medusa db:migrate

This will create the wishlist table with the following structure:

  • id: Primary key
  • customer_id: Customer identifier (searchable)
  • product_id: Product identifier (searchable)
  • created_at: Timestamp when item was added
  • updated_at: Timestamp when item was last updated

The table enforces a unique constraint on the combination of customer_id and product_id, ensuring no duplicate entries.

Store API Endpoints

Add Product to Wishlist

POST /store/wishlist

Add a product to the current customer's wishlist. The operation is idempotent - adding the same product multiple times will not create duplicates.

Authentication: Required (customer must be authenticated)

Request Body:

{
  "product_id": "prod_123"
}

Response:

{
  "wishlist_item": {
    "id": "wish_123",
    "customer_id": "cus_123",
    "product_id": "prod_123",
    "created_at": "2024-01-01T00:00:00.000Z",
    "updated_at": "2024-01-01T00:00:00.000Z"
  }
}

Example:

curl -X POST https://your-store.com/store/wishlist \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"product_id": "prod_123"}'

Remove Product from Wishlist

DELETE /store/wishlist/:product_id

Remove a product from the current customer's wishlist.

Authentication: Required (customer must be authenticated)

Response:

{
  "success": true
}

Example:

curl -X DELETE https://your-store.com/store/wishlist/prod_123 \
  -H "Authorization: Bearer YOUR_TOKEN"

Get Customer Wishlist

GET /store/wishlist

Get the current customer's wishlist. Can return either product IDs only or full product details.

Authentication: Required (customer must be authenticated)

Query Parameters:

  • include_details (boolean, default: false): If true, returns full product details. If false, returns only product IDs.

Response (IDs only):

{
  "wishlist": [
    {
      "product_id": "prod_123"
    },
    {
      "product_id": "prod_456"
    }
  ]
}

Response (with details):

{
  "wishlist": [
    {
      "id": "wish_123",
      "product_id": "prod_123",
      "product": {
        "id": "prod_123",
        "title": "Product Name",
        "handle": "product-name",
        "description": "Product description",
        "thumbnail": "https://example.com/image.jpg",
        "status": "published",
        "created_at": "2024-01-01T00:00:00.000Z",
        "updated_at": "2024-01-01T00:00:00.000Z"
      },
      "created_at": "2024-01-01T00:00:00.000Z"
    }
  ]
}

Example:

# Get only product IDs
curl https://your-store.com/store/wishlist \
  -H "Authorization: Bearer YOUR_TOKEN"

# Get full product details
curl "https://your-store.com/store/wishlist?include_details=true" \
  -H "Authorization: Bearer YOUR_TOKEN"

Admin API Endpoints

Get Wishlist Statistics

GET /admin/wishlist/stats

Get wishlist statistics showing how many users have added each product to their wishlist.

Authentication: Required (admin authentication)

Query Parameters:

  • product_id (string, optional): If provided, returns the wishlist count for that specific product only.

Response (all products):

{
  "stats": [
    {
      "product_id": "prod_123",
      "wishlist_count": 42
    },
    {
      "product_id": "prod_456",
      "wishlist_count": 15
    }
  ]
}

Response (single product):

{
  "product_id": "prod_123",
  "wishlist_count": 42
}

Example:

# Get statistics for all products
curl https://your-store.com/admin/wishlist/stats \
  -H "Authorization: Bearer ADMIN_TOKEN"

# Get statistics for a specific product
curl "https://your-store.com/admin/wishlist/stats?product_id=prod_123" \
  -H "Authorization: Bearer ADMIN_TOKEN"

Helper Functions

The plugin exposes lightweight helpers that wrap the Store API endpoints. They are ideal for server-side storefronts that need to call the wishlist endpoints while relying on the authenticated customer's session (no customer ID is ever passed in the payload).

Imports

import {
  addToWishlist,
  getWishlist,
  removeFromWishlist,
  createWishlistHelpers,
  type HelperTransportOptions,
  type WishlistItem,
} from "medusa-product-helper/helpers"

Example Usage

// Recommended: Using Bearer token for authentication
const options: HelperTransportOptions = {
  baseUrl: "https://store.example.com",
  publishableApiKey: "pk_your_publishable_api_key",
  headers: {
    Authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", // Customer JWT token
  },
}

// Add a product
await addToWishlist({ product_id: "prod_123" }, options)

// Read wishlist with details
const { wishlist } = await getWishlist({ includeDetails: true }, options)

// Remove a product
await removeFromWishlist({ product_id: "prod_123" }, options)

Alternative: Using session cookie

const options: HelperTransportOptions = {
  baseUrl: "https://store.example.com",
  publishableApiKey: "pk_your_publishable_api_key",
  headers: {
    Cookie: "connect.sid=...", // Session cookie (browser-based)
  },
}

Configuration Options

All helper calls accept the following options:

  • publishableApiKey: Required for public storefront calls when not passing a Medusa JS client. Add it from Settings → API Keys in the Medusa dashboard.
  • client: Medusa JS/SDK client instance. When provided, network requests are delegated to client.request and the client's publishable key is reused.
  • baseUrl: Base URL for the Store API (e.g., https://store.example.com). Required when a client is not provided.
  • fetchImpl: Custom fetch implementation for SSR or React Native environments. Defaults to globalThis.fetch.
  • headers: Additional headers appended to every request. Required for authenticated endpoints - include Authorization: Bearer <token> for customer authentication:
    headers: {
      "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    }

Note: All wishlist endpoints require customer authentication. Pass the Bearer token via the headers option.

You can also generate pre-configured helpers:

// Recommended: Using Bearer token
const wishlist = createWishlistHelpers({
  baseUrl: "https://store.example.com",
  publishableApiKey: "pk_your_publishable_api_key",
  headers: {
    Authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  },
})

await wishlist.addToWishlist({ product_id: "prod_123" })
const { wishlist: items } = await wishlist.getWishlist({ includeDetails: true })
await wishlist.removeFromWishlist({ product_id: "prod_123" })

Getting the Bearer Token:

// Login to get JWT token
const loginResponse = await fetch("https://store.example.com/store/auth/customer/emailpass", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-publishable-api-key": "pk_your_publishable_api_key",
  },
  body: JSON.stringify({
    email: "[email protected]",
    password: "password123",
  }),
})

const { token } = await loginResponse.json()

// Use token in wishlist helpers
const wishlist = createWishlistHelpers({
  baseUrl: "https://store.example.com",
  publishableApiKey: "pk_your_publishable_api_key",
  headers: {
    Authorization: `Bearer ${token}`,
  },
})

Using Workflows Directly

You can also use the workflows directly in your custom code:

import { addToWishlistWorkflow } from "medusa-product-helper/workflows"
import { removeFromWishlistWorkflow } from "medusa-product-helper/workflows"
import { getWishlistWorkflow } from "medusa-product-helper/workflows"

// Add to wishlist
const { result } = await addToWishlistWorkflow(container).run({
  input: {
    customer_id: "cus_123",
    product_id: "prod_123"
  }
})

// Remove from wishlist
const { result } = await removeFromWishlistWorkflow(container).run({
  input: {
    customer_id: "cus_123",
    product_id: "prod_123"
  }
})

// Get wishlist
const { result } = await getWishlistWorkflow(container).run({
  input: {
    customer_id: "cus_123",
    options: {
      includeDetails: true
    }
  }
})

Using the Module Service Directly

You can also access the wishlist module service directly:

import { WISHLIST_MODULE } from "medusa-product-helper/modules/wishlist"
import type { WishlistModuleService } from "medusa-product-helper/modules/wishlist"

// In your route or service
const wishlistService: WishlistModuleService = container.resolve(WISHLIST_MODULE)

// Add to wishlist
await wishlistService.addToWishlist(customerId, productId)

// Remove from wishlist
await wishlistService.removeFromWishlist(customerId, productId)

// Get wishlist
const wishlist = await wishlistService.getWishlist(customerId, {
  includeDetails: true
})

// Check if product is in wishlist
const isInWishlist = await wishlistService.isInWishlist(customerId, productId)

// Get wishlist counts (for admin)
const counts = await wishlistService.getWishlistCounts(['prod_123', 'prod_456'])

Getting Started

Visit the Quickstart Guide to set up a server.

Visit the Plugins documentation to learn more about plugins and how to create them.

What is Medusa

Medusa is a set of commerce modules and tools that allow you to build rich, reliable, and performant commerce applications without reinventing core commerce logic. The modules can be customized and used to build advanced ecommerce stores, marketplaces, or any product that needs foundational commerce primitives. All modules are open-source and freely available on npm.

Learn more about Medusa’s architecture and commerce modules in the Docs.

Community & Contributions

The community and core team are available in GitHub Discussions, where you can ask for support, discuss roadmap, and share ideas.

Join our Discord server to meet other community members.

Other channels