Skip to content
Published Authored byBilly Reiner

Schema · How-to

Shopify Review schema

Review is the Schema.org type for an individual rating of a product, service, or other item1. It complements AggregateRating: AggregateRating is the average; Review is one data point. On Shopify, individual Review objects are rarely necessary — AggregateRating alone produces the star snippet. Review JSON-LD is worth emitting when AI engines benefit from the full review text (positiveNotes, negativeNotes), or when the review platform provides syndicated independent third-party reviews you want to surface explicitly.

The same self-serving rule applies to individual Reviews as to AggregateRating2. A Review object emitted on the merchant's own Shopify PDP, by a reviewer who interacted with the merchant via the storefront, is self-serving. A Review syndicated from an independent third-party platform (Yotpo, Trustpilot, Reviews.io) is not. The schema is identical; the source determines eligibility.

What Review schema is

Per Schema.org v30.0, Review is 'a review of an item — for example, of a restaurant, movie, or store.' Inheritance: Thing > CreativeWork > Review. Key properties: author (Person or Organization), datePublished, reviewBody (Text — the full review text), reviewRating (Rating with ratingValue), itemReviewed (the Product being reviewed), publisher. Optional: positiveNotes, negativeNotes for extraction-friendly pro/con summaries.

Mental model: Review is one record; AggregateRating is the rollup. A Shopify PDP with 47 reviews could carry 47 Review objects plus one AggregateRating. In practice, AggregateRating alone is sufficient for the star snippet, and emitting 47 individual Reviews bloats the page. Reserve individual Review emission for cases where AI engines benefit from the review text directly — high-consideration purchases, complex products, or pages where reviews are the dominant content.

The self-serving policy applied to individual Reviews

Google's review snippet policy treats individual Reviews and AggregateRating identically: an entity reviewing itself cannot earn the star snippet. On Shopify, a Review emitted from the Shopify Product Reviews app or a custom in-house review system is self-serving — structurally valid, but no stars. A Review syndicated from an independent third-party platform (Yotpo, Stamped.io, Reviews.io, Trustpilot, Google Customer Reviews) is treated as independent.

Review fields

The Review properties Google requires for valid review snippets: author (Person with name, or Organization), datePublished (Date), reviewBody (Text — the review content) or name (Text — the review title), reviewRating (Rating with ratingValue, optionally bestRating and worstRating), itemReviewed (handled implicitly when Review is nested inside Product). Reviews missing any of these fail the Rich Results Test.

  • author — required. Person object with name. Reviewers usually anonymise to first name + last initial; Google accepts that.
  • datePublished — required. ISO 8601 date.
  • reviewBody OR name — one of the two required.
  • reviewRating — required for star eligibility. Rating object with ratingValue (1–5).
  • itemReviewed — implicit when nested in Product; explicit if emitted top-level.
  • publisher — recommended when the review comes from a third-party platform; usually omitted on first-party reviews.
  • positiveNotes / negativeNotes — optional. ItemList of strings. Useful for AI engine extraction even when stars aren't eligible.

JSON-LD example — Review array inside Product

The block below shows a Review array inside a Shopify Product block, using Liquid to iterate through review metafields from a third-party app. Limit to the most recent 3–5 reviews to keep page weight manageable.

JSON-LD Review array nested in Product, Liquid-driven from a review app's metaobject
 {%- assign reviews = product.metafields.reviews.recent.value -%} {%- if reviews.size > 0 -%} "review": [ {%- for r in reviews limit: 5 -%} { "@type": "Review", "author": { "@type": "Person", "name": "{{ r.author_name | escape }}" }, "datePublished": "{{ r.published_at | date: '%Y-%m-%d' }}", "reviewBody": "{{ r.body | escape }}", "reviewRating": { "@type": "Rating", "ratingValue": "{{ r.rating }}", "bestRating": "5", "worstRating": "1" } }{%- unless forloop.last -%},{%- endunless -%} {%- endfor -%} ] {%- endif -%} 

Validation

Rich Results Test against a PDP with individual Reviews should report Product detected, Review objects parsed, and (if the reviews are third-party) eligibility for review snippets. Common warnings: 'Either reviewBody or name is required' (the review must have either the full text or a title), 'Missing field author' (every Review needs an author Person object). Errors typically come from missing reviewRating.ratingValue.

A subtle gotcha: the Rich Results Test does not always reliably flag missing or malformed dates. If datePublished serializes as an empty string (because the Liquid metafield is null on older reviews), the markup validates, but Google's main index downstream may demote the snippet. Always branch with {% if r.published_at %} before emitting datePublished.

Shopify gotchas on Review

Three gotchas catch most Shopify Review schema work. First: emitting too many Reviews — 50+ individual Review objects bloat the page and add no rich-result benefit beyond what AggregateRating already provides. Cap the loop at 3–5. Second: emitting Reviews without escaping reviewBody, which breaks the JSON when customers leave quotes or backslashes in their text. Third: emitting Reviews from the self-serving Shopify Product Reviews app expecting stars (you won't get them; the markup still helps AI engines).

A fourth gotcha for multi-language stores: Review.inLanguage should be set when the review text is in a non-default language. Shopify's translation infrastructure handles the product copy, but customer review text retains the language the reviewer wrote in — mark it explicitly with "inLanguage": "{{ r.locale | default: shop.locale }}" so AI engines parse the language correctly.