medusa-product-helper
v0.0.32
Published
A starter for Medusa plugins.
Maintainers
Readme
Compatibility
This plugin is compatible with versions >= 2.4.0 of @medusajs/medusa.
Installation
yarn add medusa-product-helper
# or
npm install medusa-product-helperConfiguration
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,
},
// 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 ConfigModuleConfiguration 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.
Promotion Window
Configure how promotion dates are tracked using product metadata fields:
start_metadata_key: Product metadata field key storing promotion start dateend_metadata_key: Product metadata field key storing promotion end datetreat_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 productsinclude_preorder: Include preorder productsinclude_backorder: Include backorder productsinclude_gift_cards: Include gift cardspublishable_only: Only show publishable products
Rating
Configure rating-based filtering:
enabled: Enable rating filteringmin: 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 queriesmin_orders_threshold: Minimum order count threshold to be considered a best seller (default:1)
Filter Providers
Configure custom filter providers:
filterProviders: Array of file paths or module references to custom filter providersdisableBuiltInProviders: 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 - Collection Filter (
collection_id): Filter products by collection IDs - Price Range Filter (
price_range): Filter products by price range (min/max) - Promotion Window Filter (
promotion_window): Filter products by promotion date windows - 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) - Base Product Filters (
base_product): Filter by ID, handle, tags, sales channel
Using Filters
All filters are available through the /store/product-helper/products endpoint:
# Filter by category
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®ion_id=reg_123
# Sort by best seller status
GET /store/product-helper/products?order=best_seller®ion_id=reg_123
# Best sellers in last 30 days
GET /store/product-helper/products?best_seller=true&best_seller_timeframe_from=2024-01-01®ion_id=reg_123
# Best sellers with minimum order count
GET /store/product-helper/products?best_seller=true&best_seller_min_orders=5®ion_id=reg_123Custom 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 (
usersessions or secret API keys). Customer tokens receive a403response 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:migrateThis will create the wishlist table with the following structure:
id: Primary keycustomer_id: Customer identifier (searchable)product_id: Product identifier (searchable)created_at: Timestamp when item was addedupdated_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): Iftrue, returns full product details. Iffalse, 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 toclient.requestand 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: Customfetchimplementation for SSR or React Native environments. Defaults toglobalThis.fetch.headers: Additional headers appended to every request. Required for authenticated endpoints - includeAuthorization: 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.
