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-review-rating

v0.0.37

Published

A starter for Medusa plugins.

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

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, and rejected statuses
  • Single or Multiple Reviews: By default, one review per product per customer; set multiple_rating: true in 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-rating

Or with yarn:

yarn add medusa-review-rating

Step 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:migrate

Note: The migration will:

  • Create the review table
  • Add total_rating_count and total_rating_sum columns to the product table (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 filters
  • retrieveReview(id) - Get a single review
  • createReview(data) - Create a new review
  • updateReview(id, data) - Update a review
  • deleteReview(id) - Delete a review
  • calculateProductRating(productId) - Recalculate product rating
  • verifyPurchase(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/products

Response: 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/:id

Response:

{
  "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/rating

Response:

{
  "rating": {
    "average_rating": 4.53,
    "total_reviews": 15,
    "total_rating_sum": 68
  }
}

Review Endpoints

Submit a Review
POST /store/reviews

Authentication: 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: truestatus: "approved"
  • auto_approve: falsestatus: "pending"
Get Product Reviews
GET /store/products/:id/reviews

Query Parameters:

  • status (optional) - Filter by status: approved, pending, rejected
  • limit (optional) - Number of reviews per page
  • offset (optional) - Pagination offset

Behavior:

  • Unauthenticated users: Only see approved reviews
  • Authenticated users: See their own reviews (all statuses) + other users' approved reviews

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/:id

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": 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/me

Authentication: Required

Query Parameters:

  • product_id (optional) - Filter reviews for a specific product
  • status (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/me

Authentication: 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/reviews

Query Parameters:

  • product_id (optional) - Filter by product ID
  • customer_id (optional) - Filter by customer ID
  • status (optional) - Filter by status: approved, pending, rejected
  • limit (optional) - Number of reviews per page
  • offset (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/:id

Request 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/:id

Response:

{
  "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)    // 68

Frontend - 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 build

Watch Mode

npm run dev
# or
yarn dev

Testing

npm test
# or
yarn test

Generate Migrations

npx medusa plugin:db:generate

License

MIT