medusa-review-rating
v0.0.37
Published
A starter for Medusa plugins.
Maintainers
Readme
Medusa Review & Rating Plugin
A comprehensive Medusa v2 plugin that adds product reviews and ratings functionality to your store. Built as a Medusa module with workflows, API routes, and storefront helpers.
Table of Contents
- Overview
- Features
- Installation
- Configuration
- Module Structure
- API Reference
- Storefront Helpers
- Integration Examples
- Development
Overview
This plugin provides a complete review and rating system for Medusa v2 stores. It includes:
- Review Module: A Medusa module (
reviews) that manages review data and business logic - Workflows: Automated workflows for creating, updating, and deleting reviews
- API Routes: RESTful endpoints for both storefront and admin operations
- Product Integration: Automatic rating calculation and storage in product table
- Storefront Helpers: Type-safe helper functions for frontend integration
Features
- ✅ Product Reviews: Customers can submit reviews with ratings (1-5), title, description, and images
- ✅ Product Ratings: Automatic calculation and storage of ratings in product table (
total_rating_count,total_rating_sum,average_rating) - ✅ Review Status Management: Support for
pending,approved, andrejectedstatuses - ✅ Single or Multiple Reviews: By default, one review per product per customer; set
multiple_rating: truein config to allow multiple reviews per product - ✅ Purchase Verification: Optional verification that customer purchased the product before reviewing
- ✅ Auto-Approval: Configurable auto-approval of reviews
- ✅ Admin Management: Full admin interface to manage reviews
- ✅ Storefront API: Public and authenticated endpoints for customers
- ✅ Type-Safe Helpers: Storefront helper functions with TypeScript support
Installation
Step 1: Install the Package
npm install medusa-review-ratingOr with yarn:
yarn add medusa-review-ratingStep 2: Configure Medusa
Add the plugin to your medusa-config.js or medusa-config.ts:
const { defineConfig } = require("@medusajs/framework/utils")
module.exports = defineConfig({
// ... your existing config
modules: {
reviews: {
resolve: "medusa-review-rating",
options: {
auto_approve: false, // Default: true
verify_purchase: false, // Default: false
multiple_rating: false // Default: false - one review per product; set true to allow multiple
}
},
},
plugins: [
{
resolve: "medusa-review-rating",
},
],
})Step 3: Run Migrations
Run database migrations to create the review table and add rating columns to products:
npx medusa db:migrateNote: The migration will:
- Create the
reviewtable - Add
total_rating_countandtotal_rating_sumcolumns to theproducttable (if they don't exist)
Configuration
Configure the plugin behavior in your medusa-config.js:
modules: {
reviews: {
resolve: "medusa-review-rating",
options: {
// Auto-approve all reviews (default: true)
// When true, reviews are immediately approved
// When false, reviews require admin approval
auto_approve: false,
// Require verified purchase (default: false)
// When true, only customers who purchased the product can review
// When false, any customer can review
verify_purchase: false,
// Allow multiple reviews per product (default: false)
// When false, each customer can post only one review per product; duplicate attempts return 409
// When true, customers can post multiple reviews for the same product
multiple_rating: true
}
},
}Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| auto_approve | boolean | true | Automatically approve reviews when submitted |
| verify_purchase | boolean | false | Require customer to have purchased the product before reviewing |
| multiple_rating | boolean | false | When false, each customer can post only one review per product (duplicate returns 409). When true, multiple reviews per product are allowed. |
Important for multiple_rating: The option must reach the reviews module. In medusa-config, define the module with options (not only the plugin). For example:
// In medusa-config.js / medusa-config.ts
module.exports = defineConfig({
modules: {
reviews: {
resolve: "medusa-review-rating",
options: {
multiple_rating: true, // or multipleRating: true
},
},
},
plugins: [{ resolve: "medusa-review-rating" }],
})If you only add the plugin under plugins and do not have modules.reviews with options, the module may not receive the config and will default to one review per product.
Module Structure
This plugin is built as a Medusa Module, which means it can be used throughout your Medusa application.
Module Constant
The module exports a constant for service resolution:
import { REVIEW_MODULE } from "medusa-review-rating"
// Use in workflows, steps, or custom code
const reviewService = container.resolve<ReviewModuleService>(REVIEW_MODULE)Module Service
The ReviewModuleService provides the following methods:
listReviews(options)- List reviews with filtersretrieveReview(id)- Get a single reviewcreateReview(data)- Create a new reviewupdateReview(id, data)- Update a reviewdeleteReview(id)- Delete a reviewcalculateProductRating(productId)- Recalculate product ratingverifyPurchase(customerId, productId)- Check if customer purchased product
Review Model
The review model includes:
{
id: string
product_id: string
customer_id: string
rating: number (1-5)
title: string | null
description: string | null
images: string[] | null
verified_purchase: boolean
status: "pending" | "approved" | "rejected"
created_at: Date
updated_at: Date
deleted_at: Date | null
}API Reference
Storefront Endpoints
All storefront endpoints are prefixed with /store.
Product Rating Endpoints
Get Product List (with ratings)
GET /store/productsResponse: Standard Medusa product list with additional rating fields:
{
"products": [
{
"id": "prod_123",
"title": "Product Name",
"total_rating_count": 15,
"total_rating_sum": 68,
"average_rating": 4.53
}
],
"count": 10,
"offset": 0,
"limit": 20
}Get Single Product (with rating)
GET /store/products/:idResponse:
{
"product": {
"id": "prod_123",
"title": "Product Name",
"total_rating_count": 15,
"total_rating_sum": 68,
"average_rating": 4.53
}
}Get Product Rating Only
GET /store/products/:id/ratingResponse:
{
"rating": {
"average_rating": 4.53,
"total_reviews": 15,
"total_rating_sum": 68
}
}Review Endpoints
Submit a Review
POST /store/reviewsAuthentication: Required (customer must be logged in)
Request Body:
{
"product_id": "prod_123",
"rating": 5,
"title": "Great product!",
"description": "This product exceeded my expectations...",
"images": ["url1", "url2"]
}Response:
{
"review": {
"id": "rev_123",
"product_id": "prod_123",
"customer_id": "cus_123",
"rating": 5,
"title": "Great product!",
"description": "This product exceeded my expectations...",
"images": ["url1", "url2"],
"verified_purchase": false,
"status": "pending",
"created_at": "2024-11-21T08:00:00.000Z",
"updated_at": "2024-11-21T08:00:00.000Z"
}
}Note: The status field depends on auto_approve configuration:
auto_approve: true→status: "approved"auto_approve: false→status: "pending"
Get Product Reviews
GET /store/products/:id/reviewsQuery Parameters:
status(optional) - Filter by status:approved,pending,rejectedlimit(optional) - Number of reviews per pageoffset(optional) - Pagination offset
Behavior:
- Unauthenticated users: Only see
approvedreviews - Authenticated users: See their own reviews (all statuses) + other users'
approvedreviews
Response:
{
"reviews": [
{
"id": "rev_123",
"product_id": "prod_123",
"customer_id": "cus_123",
"rating": 5,
"title": "Great product!",
"description": "This product exceeded my expectations...",
"images": ["url1", "url2"],
"verified_purchase": true,
"status": "approved",
"created_at": "2024-11-21T08:00:00.000Z"
}
]
}Get Single Review
GET /store/reviews/:idResponse:
{
"review": {
"id": "rev_123",
"product_id": "prod_123",
"customer_id": "cus_123",
"rating": 5,
"title": "Great product!",
"description": "This product exceeded my expectations...",
"images": ["url1", "url2"],
"verified_purchase": true,
"status": "approved",
"created_at": "2024-11-21T08:00:00.000Z",
"updated_at": "2024-11-21T08:00:00.000Z"
}
}Get Current Customer's Reviews
GET /store/reviews/meAuthentication: Required
Query Parameters:
product_id(optional) - Filter reviews for a specific productstatus(optional) - Filter by status:pending,approved,rejected
Response:
{
"reviews": [
{
"id": "rev_123",
"product_id": "prod_123",
"rating": 5,
"title": "Great product!",
"status": "approved"
}
],
"summary": {
"total_reviews": 3,
"average_rating": 4.33,
"total_rating_sum": 13
}
}Get Current Customer's Review for Product
GET /store/products/:id/reviews/meAuthentication: Required
Response:
{
"reviews": [
{
"id": "rev_999",
"product_id": "prod_123",
"rating": 4,
"title": "Good product",
"status": "pending"
}
],
"summary": {
"product_id": "prod_123",
"total_reviews": 1,
"average_rating": 4,
"total_rating_sum": 4
}
}Admin Endpoints
All admin endpoints are prefixed with /admin and require admin authentication.
List All Reviews
GET /admin/reviewsQuery Parameters:
product_id(optional) - Filter by product IDcustomer_id(optional) - Filter by customer IDstatus(optional) - Filter by status:approved,pending,rejectedlimit(optional) - Number of reviews per pageoffset(optional) - Pagination offset
Response:
{
"reviews": [
{
"id": "rev_123",
"product_id": "prod_123",
"customer_id": "cus_123",
"rating": 5,
"title": "Great product!",
"description": "This product exceeded my expectations...",
"status": "pending",
"verified_purchase": false,
"created_at": "2024-11-21T08:00:00.000Z"
}
],
"count": 10,
"offset": 0,
"limit": 20
}Update Review Status
POST /admin/reviews/:idRequest Body:
{
"status": "approved"
}Accepted values: approved, rejected, pending
Response:
{
"review": {
"id": "rev_123",
"status": "approved",
"updated_at": "2024-11-21T09:00:00.000Z"
}
}Note: Updating status to approved automatically recalculates the product's rating.
Delete Review
DELETE /admin/reviews/:idResponse:
{
"id": "rev_123",
"deleted": true
}Note: Deleting a review automatically recalculates the product's rating.
Storefront Helpers
This plugin provides type-safe helper functions for frontend integration, similar to other Medusa modules.
Installation
The helpers are exported from the package:
import {
submitReview,
listCustomerReviews,
listCustomerProductReviews,
listProductReviews,
getProductRating,
type StorefrontHelperOptions,
type ReviewDTO,
} from "medusa-review-rating/helpers"Basic Usage
// Common options - can be reused across all helper calls
const options: StorefrontHelperOptions = {
baseUrl: "https://store.myshop.com",
publishableApiKey: "pk_your_publishable_api_key_here", // Required for public endpoints
}
// Submit a review (requires authentication)
await submitReview(
{
product_id: "prod_123",
rating: 5,
title: "Amazing product",
description: "Highly recommend",
},
{
...options,
headers: {
Authorization: "Bearer your_jwt_token_here", // Required
},
}
)
// Fetch all reviews authored by the current customer (requires authentication)
const myReviews = await listCustomerReviews(
{ status: "approved" },
{
...options,
headers: {
Authorization: "Bearer your_jwt_token_here",
},
}
)
// Fetch only the current customer's review for a specific product (requires authentication)
const myProductReviews = await listCustomerProductReviews("prod_123", {
...options,
headers: {
Authorization: "Bearer your_jwt_token_here",
},
})
// Public product reviews (only requires publishable API key)
const productReviews = await listProductReviews("prod_123", options)
// Get product rating summary (only requires publishable API key)
const rating = await getProductRating("prod_123", options)Using with Medusa JS SDK
import { Medusa } from "@medusajs/js-sdk"
import { listProductReviews } from "medusa-review-rating/helpers"
const medusa = new Medusa({
baseUrl: "https://store.myshop.com",
publishableKey: "pk_your_publishable_api_key_here",
})
// Use the SDK client instead of baseUrl + publishableApiKey
const productReviews = await listProductReviews("prod_123", {
client: medusa,
})Helper Options
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| publishableApiKey | string | Yes (for public endpoints) | Your Medusa publishable API key. Found in Settings → API Key Management |
| baseUrl | string | Yes | Storefront API origin (e.g., https://store.myshop.com) |
| client | Medusa | No | Pass an initialized Medusa JS SDK client to reuse its transport |
| fetchImpl | Function | No | Custom fetch implementation (for SSR, React Native, etc.) |
| headers | Record<string, string> | No | Additional headers merged into every request |
Authentication
Public Endpoints (only require publishableApiKey):
listProductReviews()getProductRating()
Authenticated Endpoints (require publishableApiKey + customer authentication):
submitReview()listCustomerReviews()listCustomerProductReviews()
For authenticated requests, include the customer's JWT token or session cookie:
// Option 1: Using JWT token (recommended)
const authenticatedOptions: StorefrontHelperOptions = {
baseUrl: "https://store.myshop.com",
publishableApiKey: "pk_your_publishable_api_key_here",
headers: {
Authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", // JWT token from customer login
},
}
await submitReview(
{
product_id: "prod_123",
rating: 5,
title: "Amazing product",
description: "Highly recommend",
},
authenticatedOptions
)
// Option 2: Using session cookie
const authenticatedOptionsWithCookie: StorefrontHelperOptions = {
baseUrl: "https://store.myshop.com",
publishableApiKey: "pk_your_publishable_api_key_here",
headers: {
Cookie: "connect.sid=your_session_cookie",
},
}
const myReviews = await listCustomerReviews({}, authenticatedOptionsWithCookie)Integration Examples
Frontend - Display Product with Rating
// Fetch product (rating included automatically)
const response = await fetch('https://store.myshop.com/store/products/prod_123', {
headers: {
'x-publishable-key': 'pk_your_publishable_api_key_here'
}
})
const { product } = await response.json()
console.log(product.average_rating) // 4.53
console.log(product.total_rating_count) // 15
console.log(product.total_rating_sum) // 68Frontend - Submit Review
// Using fetch
const review = await fetch('https://store.myshop.com/store/reviews', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer customer_jwt_token',
'x-publishable-key': 'pk_your_publishable_api_key_here'
},
body: JSON.stringify({
product_id: 'prod_123',
rating: 5,
title: 'Great product!',
description: 'Highly recommend'
})
}).then(r => r.json())
// Using helper function
import { submitReview } from 'medusa-review-rating/helpers'
const review = await submitReview(
{
product_id: 'prod_123',
rating: 5,
title: 'Great product!',
description: 'Highly recommend'
},
{
baseUrl: 'https://store.myshop.com',
publishableApiKey: 'pk_your_publishable_api_key_here',
headers: {
Authorization: 'Bearer customer_jwt_token'
}
}
)Frontend - Display Product Reviews
import { listProductReviews } from 'medusa-review-rating/helpers'
const { reviews } = await listProductReviews('prod_123', {
baseUrl: 'https://store.myshop.com',
publishableApiKey: 'pk_your_publishable_api_key_here'
})
reviews.forEach(review => {
console.log(`${review.rating}/5 - ${review.title}`)
console.log(review.description)
})Admin - Approve Review
await fetch('https://store.myshop.com/admin/reviews/rev_123', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_ADMIN_TOKEN'
},
body: JSON.stringify({ status: 'approved' })
})Development
Build
npm run build
# or
yarn buildWatch Mode
npm run dev
# or
yarn devTesting
npm test
# or
yarn testGenerate Migrations
npx medusa plugin:db:generateLicense
MIT
