Skip to content

Product Reviews

About

The productReviews query retrieves a collection of product reviews with filtering and pagination support. Use this query to:

  • Display product reviews on product detail pages
  • Filter reviews by product, status, and rating
  • Build review listing pages with cursor pagination
  • Display customer feedback and testimonials
  • Calculate average ratings and review counts

Two behaviours decide what you get back:

BehaviourWhat it means
Approved only, by defaultOmitting status returns approved reviews and nothing else — the right default for a product page, since a customer's review stays pending until an admin approves it. Pass status to override, which is how moderation tooling asks for "pending" or "disapproved".
Fixed order, oldest firstReviews come back in review-ID order and there is no sort argument. Sort in the client when a product page needs newest or highest-rated first.

Arguments

ArgumentTypeRequiredDescription
product_idInt❌ NoRestrict the result to one product's reviews. Omit to read reviews across the whole catalog.
statusString❌ NoFilter by review status ("pending", "approved", "disapproved"). Defaults to "approved".
ratingInt❌ NoFilter by rating value (1-5 stars).
firstInt❌ NoNumber of results to return (forward pagination). Default: 30
afterString❌ NoPagination cursor for forward navigation. Use with first.
lastInt❌ NoNumber of results for backward pagination. Default: 30
beforeString❌ NoPagination cursor for backward navigation. Use with last.

Supplying several filters narrows the result — they combine, they never widen the set.

Possible Returns

FieldTypeDescription
edges[ProductReviewEdge]Review edges for the current page.
edges.nodeProductReviewA single review — fields below.
edges.cursorString!Cursor for this review, used as after on the next request.
pageInfoProductReviewPageInfo!Pagination metadata.
pageInfo.hasNextPageBooleanWhether more reviews follow the current page.
pageInfo.hasPreviousPageBooleanWhether reviews precede the current page.
pageInfo.startCursorStringCursor of the first review on the page.
pageInfo.endCursorStringCursor of the last review on the page.
totalCountInt!Total reviews matching the filters.

Review Fields

FieldTypeDescription
idID!IRI-style review identifier.
_idInt!Numeric review ID.
nameString!Name of the customer who wrote the review.
titleString!Review title or headline.
ratingInt!Star rating, 1 to 5.
commentStringReview body text.
statusString!Approval status ("pending", "approved", "disapproved").
attachmentsStringImages the customer attached to the review, as a JSON value. null when none were uploaded.
createdAtStringISO 8601 timestamp of when the review was submitted.
updatedAtStringISO 8601 timestamp of the last change.

Review Status

StatusDescription
"pending"Awaiting moderation approval
"approved"Published and visible on the storefront
"disapproved"Declined and not published

Use Cases

1. Reviews block on a product page

Scope to the product and let the default status do the filtering — nothing pending or disapproved reaches the page.

graphql
query productPageReviews($productId: Int!) {
  productReviews(product_id: $productId, first: 10) {
    totalCount
    edges {
      node {
        _id
        name
        title
        rating
        comment
        createdAt
      }
      cursor
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

Use totalCount for the "N reviews" heading, and feed endCursor into the next request's after when the shopper asks for more.

2. Star breakdown for the ratings summary

There is no aggregate field, so the rating histogram is five counts. Alias them into one request and read only totalCount from each:

graphql
query ratingBreakdown($productId: Int!) {
  five:  productReviews(product_id: $productId, rating: 5, first: 1) { totalCount }
  four:  productReviews(product_id: $productId, rating: 4, first: 1) { totalCount }
  three: productReviews(product_id: $productId, rating: 3, first: 1) { totalCount }
  two:   productReviews(product_id: $productId, rating: 2, first: 1) { totalCount }
  one:   productReviews(product_id: $productId, rating: 1, first: 1) { totalCount }
}

Keep first: 1 rather than first: 0 — a zero page size fails the request.

3. Confirming a review the shopper just submitted

A review created through Create Product Review is pending, so it will not appear in the block above. Ask for the pending set to show a "your review is awaiting approval" state instead of leaving the shopper wondering where it went.

graphql
query pendingForProduct($productId: Int!) {
  productReviews(product_id: $productId, status: "pending", first: 5) {
    totalCount
    edges {
      node {
        _id
        title
        rating
        createdAt
      }
    }
  }
}

4. Newest-first review lists

The API returns reviews in review-ID order, so a "most recent" tab is built in the client: request the page, then sort the nodes by createdAt descending before rendering.

Best Practices

  1. Omit status for anything customer-facing — the default already restricts the result to approved reviews, so a product page needs no filter of its own. Send status: "pending" only to confirm a shopper's own freshly submitted review, never to build a public list
  2. Scope to a product — pass product_id on a product detail page, otherwise the query reads reviews across the whole catalog
  3. Paginate — a popular product accumulates hundreds of reviews; page through them with first and after rather than raising first
  4. Sort client-side — the API returns reviews oldest first and offers no sort argument, so reorder in the client if the page needs newest or highest-rated first
  5. Cache the result — reviews change infrequently, so they cache well per product and status

Released under the MIT License.