Ratings and Reviews quick start
Show approved product reviews below the product details on your product pages. The list shows 20 reviews per page and includes page controls when more reviews are available.
The setup uses four parts: the drop-in package, an import map entry, an initializer, and the Product Reviews block. If your storefront already has scripts/initializers/ratings-reviews.js and blocks/product-reviews/, skip to Check the result.
Before you begin
Section titled “Before you begin”- Turn on product reviews and approve at least one review in the Admin. See Before you begin on the overview page.
- Run your storefront locally. See Run your storefront locally.
Add the drop-in to your storefront
Section titled “Add the drop-in to your storefront”Install the package and create the files that connect the review list to your storefront.
-
Install the drop-in package from the root of your storefront project.
Terminal window # Adds the package to package.json with an exact version.npm install @dropins/storefront-ratings-reviews# Copies the drop-in files into scripts/__dropins__/ for the browser.npm run install:dropinsYour storefront’s
.npmrcdisables package install scripts to protect against supply-chain attacks. Runnpm run install:dropinsto copy the files thatnpm installskips. After it finishes, confirm thatscripts/__dropins__/storefront-ratings-reviews/exists. -
In
head.html, add the drop-in to the existing import map.head.html <script nonce="aem" type="importmap">{"imports": {"@dropins/storefront-pdp/": "/scripts/__dropins__/storefront-pdp/","@dropins/storefront-ratings-reviews/": "/scripts/__dropins__/storefront-ratings-reviews/","@dropins/storefront-recommendations/": "/scripts/__dropins__/storefront-recommendations/"}}</script>Add only the
@dropins/storefront-ratings-reviews/entry and keep your other entries. It maps imports in block code to the installed files underscripts/__dropins__/storefront-ratings-reviews/. -
Create the initializer at
scripts/initializers/ratings-reviews.js.scripts/initializers/ratings-reviews.js import { initializers } from '@dropins/tools/initializer.js';import { initialize, setEndpoint } from '@dropins/storefront-ratings-reviews/api.js';import { initializeDropin } from './index.js';import { CORE_FETCH_GRAPHQL, fetchPlaceholders } from '../commerce.js';// Loads label overrides from the placeholders/ratings-reviews.json sheet.const labels = await fetchPlaceholders('placeholders/ratings-reviews.json');await initializeDropin(async () => {// Reviews and review settings come from Commerce GraphQL, not Catalog Service.setEndpoint(CORE_FETCH_GRAPHQL);// Loads the store's review settings and saves them in the drop-in config.return initializers.mountImmediately(initialize, {langDefinitions: {default: { ...labels },},});})();CORE_FETCH_GRAPHQLsupplies your Adobe Commerce GraphQL endpoint and store headers.fetchPlaceholdersloads custom labels fromplaceholders/ratings-reviews.json.mountImmediatelywaits for the store’s review settings to load before initialization finishes.
For configuration options, see Ratings and Reviews initialization.
-
Create the block script at
blocks/product-reviews/product-reviews.js.blocks/product-reviews/product-reviews.js import { config, getProductReviews } from '@dropins/storefront-ratings-reviews/api.js';import { render as reviewsRenderer } from '@dropins/storefront-ratings-reviews/render.js';import { Review } from '@dropins/storefront-ratings-reviews/containers/Review.js';import { Pagination } from '@dropins/storefront-ratings-reviews/containers/Pagination.js';import { readBlockConfig } from '../../scripts/aem.js';import { getProductSku } from '../../scripts/commerce.js';// Runs the initializer before the block reads the review settings.import '../../scripts/initializers/ratings-reviews.js';export default async function decorate(block) {const { sku: authoredSku } = readBlockConfig(block);block.replaceChildren();block.hidden = true;// Keeps the block hidden and skips requests when reviews are turned off.if (config.getConfig()?.isProductReviewsEnabled !== true || !block.isConnected) return;// An authored sku row wins; otherwise use the SKU of the current product page.const sku = authoredSku?.trim() || getProductSku();if (!sku) throw new Error('A product SKU is required to display product reviews.');// A unique scope keeps this list's events separate from other review lists on the page.const scope = `product-reviews-${crypto.randomUUID()}`;const fragment = document.createRange().createContextualFragment(`<div class="product-reviews__list"></div><div class="product-reviews__pagination"></div>`);const reviews = fragment.querySelector('.product-reviews__list');const pagination = fragment.querySelector('.product-reviews__pagination');block.appendChild(fragment);block.hidden = false;await Promise.all([reviewsRenderer.render(Review, { scope })(reviews),reviewsRenderer.render(Pagination, { scope })(pagination),]);if (!block.isConnected) return;// Not awaited, so slow reviews never delay the rest of the page.getProductReviews(sku, { pageSize: 20, currentPage: 1, scope }).catch((error) => {console.error('[Product Reviews] Failed to load reviews:', error);});}- Initial request:
getProductReviewsloads the first page and publishes events for both containers. - Separate lists: Each block uses a unique
scopeto keep its events separate from other review lists on the page. - Later requests:
Paginationrequests pages when a shopper uses the page controls.Reviewretries a failed request when a shopper selects Try again.
- Initial request:
-
Create the block styles at
blocks/product-reviews/product-reviews.css.blocks/product-reviews/product-reviews.css /* Separates the reviews section from the content above it. */.product-reviews {margin-block-start: var(--spacing-xxbig);}/* Keeps the block hidden while reviews are turned off or loading has not started. */.product-reviews[hidden] {display: none;}.product-reviews .product-reviews__pagination {min-width: 0;margin-block: var(--spacing-medium);}/* Removes the pagination spacing when there is only one page. */.product-reviews .product-reviews__pagination:has(.ratingsreviews-pagination[hidden]) {display: none;} -
Place the block on product pages. Choose one option:
- Author the block: Add a Product Reviews block to your product page template or another page. See Product Reviews block.
- Add it automatically: Append the block when the storefront builds a product page from its SKU. Follow Add reviews to product pages automatically.
Add reviews to product pages automatically
Section titled “Add reviews to product pages automatically”When the storefront builds a Product Details block from a SKU in page metadata or the product URL, it can also add reviews. Extend the automatic block creation (auto-blocking) code to append a Product Reviews section.
-
In
scripts/commerce.js, add a function that appends a Product Reviews section to the page.scripts/commerce.js /*** Appends a product-reviews section unless reviews are already authored.* @param {Element} main The main element*/export function buildProductRatingsAndReviews(main) {// Respects an authored Product Reviews block, so the page never shows two lists.if (main.querySelector('.product-reviews')) return;const section = document.createElement('div');section.append(buildBlock('product-reviews', { elems: [] }));main.append(section);}scripts/commerce.jsalready importsbuildBlockfrom./aem.js.- The check for
.product-reviewskeeps an authored block from appearing alongside a second, automatically added list.
-
In
scripts/scripts.js, import the function and call it right afterbuildPDPBlock(main).scripts/scripts.js import {buildPDPBlock,// Add this import next to buildPDPBlock.buildProductRatingsAndReviews,getProductSku,loadCommerceEager,loadCommerceLazy,} from './commerce.js';function buildAutoBlocks(main) {try {// ...existing auto-blocking code...const pdpBlock = document.querySelector('.product-details');if (metaSku && !pdpBlock) {buildPDPBlock(main);// Adds the reviews section at the end of the page.buildProductRatingsAndReviews(main);}} catch (error) {console.error('Auto Blocking failed', error);}}Keep your existing imports and auto-blocking code, including the SKU lookup that defines
metaSku. Add only thebuildProductRatingsAndReviewsimport and the call afterbuildPDPBlock(main).
This automatic placement runs only when the storefront creates the Product Details block. For pages with an authored Product Details block, add Product Reviews to the page or template instead.
Add a Write review button
Section titled “Add a Write review button”Add a Write review button above the list to let shoppers submit reviews. The button opens the WriteReview form in a modal dialog over the page. New reviews appear in the list after approval in the Admin.
-
Replace
blocks/product-reviews/product-reviews.jswith this version to keep the review list and add the button and modal.blocks/product-reviews/product-reviews.js import { events } from '@dropins/tools/event-bus.js';import { Button, provider as UI } from '@dropins/tools/components.js';import { config, getProductReviews } from '@dropins/storefront-ratings-reviews/api.js';import { render as reviewsRenderer } from '@dropins/storefront-ratings-reviews/render.js';import { Review } from '@dropins/storefront-ratings-reviews/containers/Review.js';import { WriteReview } from '@dropins/storefront-ratings-reviews/containers/WriteReview.js';import { Pagination } from '@dropins/storefront-ratings-reviews/containers/Pagination.js';import { readBlockConfig } from '../../scripts/aem.js';import {CUSTOMER_LOGIN_PATH,checkIsAuthenticated,fetchPlaceholders,getProductSku,rootLink,} from '../../scripts/commerce.js';import createModal from '../modal/modal.js';import '../../scripts/initializers/ratings-reviews.js';// Shows the product name and image in the form when this page's product is the one being reviewed.function getPageProduct(sku) {const product = events.lastPayload('pdp/data');if (!product || product.sku !== sku) return {};const image = product.images?.[0];return {productName: product.name,productImage: image?.url ? { src: image.url, alt: image.label || product.name } : undefined,};}async function openWriteReview(sku) {const content = document.createElement('div');content.className = 'product-reviews__modal-content';const modal = await createModal([content]);// Opens the modal first, because the form loads rating categories before it appears.modal.showModal();await reviewsRenderer.render(WriteReview, {sku,...getPageProduct(sku),isAuthenticated: checkIsAuthenticated(),routeSignIn: () => rootLink(CUSTOMER_LOGIN_PATH),})(content);}export default async function decorate(block) {const { sku: authoredSku } = readBlockConfig(block);block.replaceChildren();block.hidden = true;// Also hides the Write review button when reviews are turned off.if (config.getConfig()?.isProductReviewsEnabled !== true || !block.isConnected) return;const sku = authoredSku?.trim() || getProductSku();if (!sku) throw new Error('A product SKU is required to display product reviews.');const scope = `product-reviews-${crypto.randomUUID()}`;const fragment = document.createRange().createContextualFragment(`<div class="product-reviews__write-review"></div><div class="product-reviews__list"></div><div class="product-reviews__pagination"></div>`);const labels = await fetchPlaceholders('placeholders/ratings-reviews.json');const reviews = fragment.querySelector('.product-reviews__list');const pagination = fragment.querySelector('.product-reviews__pagination');block.appendChild(fragment);block.hidden = false;await UI.render(Button, {children: labels.Global?.WriteReview ?? 'Write review',variant: 'secondary',onClick: () => openWriteReview(sku),})(block.querySelector('.product-reviews__write-review'));await Promise.all([reviewsRenderer.render(Review, { scope })(reviews),reviewsRenderer.render(Pagination, { scope })(pagination),]);if (!block.isConnected) return;getProductReviews(sku, { pageSize: 20, currentPage: 1, scope }).catch((error) => {console.error('[Product Reviews] Failed to load reviews:', error);});}- Button label:
Global.WriteReviewinplaceholders/ratings-reviews.jsonsupplies the text. If the key is missing, the button shows Write review. - Shopper access:
checkIsAuthenticated()tells the form whether the shopper is signed in. When guests cannot write reviews, signed-out shoppers see a Sign in button that opens your sign-in page.
- Button label:
-
Add these rules to
blocks/product-reviews/product-reviews.css:blocks/product-reviews/product-reviews.css .product-reviews__write-review {margin-block-end: var(--spacing-medium);}/* footer.css offsets every modal's close button by 40px with !important; restore the corner for this modal. */.modal:has(.product-reviews__modal-content) .close-button {top: 0 !important;right: 0 !important;}/* Keeps the form clear of the close button. */.product-reviews__modal-content {padding-block-start: var(--spacing-big);}
To protect the form with reCAPTCHA, configure Enable for Product Review in the Admin reCAPTCHA settings. Your storefront already configures the reCAPTCHA library. See reCAPTCHA.
Check the result
Section titled “Check the result”Open a product page for a product with approved reviews.
- The Reviews heading appears above the review list.
- Each review shows the reviewer’s name, stars or Not rated, the date, the title, and the text.
- Products with more than 20 approved reviews show page controls below the list.
- If you added the Write review button, select it and submit a review. Confirm that the success message says the review is waiting for approval. Approve the review in the Admin to see it in the list.
If the reviews section does not appear, check these common causes:
| What you see | Likely cause | What to do |
|---|---|---|
| No reviews section and no errors | Product reviews are turned off for the store | Turn on product reviews in the Admin, then reload the page. |
Console error [Ratings Reviews] Failed to fetch store config: | The initializer could not load review settings, so reviews stay off | Check the Commerce GraphQL endpoint and store headers in your storefront configuration. |
Console error A product SKU is required to display product reviews. | The block could not find a SKU | Add a sku row to the authored block, or place the block on a product page. |
| No reviews on this page. | The product has no approved reviews | Approve a review in the Admin. |
| Unable to load reviews. with a Try again button | The reviews request failed | Confirm that your Commerce GraphQL endpoint provides the reviews query. |
Quick reference
Section titled “Quick reference”- Package:
@dropins/storefront-ratings-reviews - Initializer:
import '../../scripts/initializers/ratings-reviews.js'; - Review container:
import { Review } from '@dropins/storefront-ratings-reviews/containers/Review.js'; - Pagination container:
import { Pagination } from '@dropins/storefront-ratings-reviews/containers/Pagination.js'; - WriteReview container:
import { WriteReview } from '@dropins/storefront-ratings-reviews/containers/WriteReview.js'; - Render provider:
import { render } from '@dropins/storefront-ratings-reviews/render.js'; - API function:
import { getProductReviews } from '@dropins/storefront-ratings-reviews/api.js';
Next steps
Section titled “Next steps”- Show average ratings on the product page header.
- Show ratings on product listing cards and recommendation cards.
- Customize review rows with slots or change labels in the dictionary.