A review system: trust, UI, and features

Four steps of a review system: like or dislike, stars, several attributes, and a product card with text and rating bars

Not long ago I was designing a review system, and the core idea was this: the site should become more trustworthy because of words from real people. Not because of a pretty description from a copywriter. Because of user content - what someone wrote after they had already bought, eaten, hired, or tried.

Reviews look like a two-day job: a table, stars, a form. That is what I thought until I sat down to design them for real. Almost all of the complexity sits in three places: who is allowed to write at all, how the card looks on screen, and who decides that the text can be shown to people.

Below is an engineering walkthrough. I am writing it so it reads as being about any review system: products in a shop, restaurants, couriers, contractors, employees. The words change, the structure does not. Along the way I will compare other people's screens: Euronics (a product), Amazon (a huge catalog), Airbnb (a stay), Glassdoor (an employer), and Google (search results). The object is different; the list of questions is almost the same.

Why this matters for the business (and why it shapes the schema)

Three goals. Each one has an emotion for the buyer and a hard requirement for the database.

🤝

Trust

A person buys from a stranger if they can see other people's experience. So a review must point at a real purchase, booking, or shift - not at "an anonymous person from the internet".

🔎

Search

Stars in Google results are a free click. The search engine does not take a picture; it takes two numbers: the average rating and how many reviews there are. Those numbers have to be cheap to compute on every card.

↩️

Fewer returns

"Assembled it in an hour" or "the portion was cold" describes the object more precisely than the manufacturer's card. So the text matters more than bare stars, and the text has to be checked by a human.

Trust needs proof. Search needs an aggregate (average + count). Fewer returns need live text and moderation.

The obvious rule: you cannot write a review out of thin air

The cheapest decision, and the one that cuts off 90% of future problems: a review is created only from a confirmed fact. A paid order. A completed booking. A closed shift. An accepted project.

Why this decision lives in the database, not in "antifraud later". If the order column is required and points at a real order, fake reviews become impossible by construction. I do not need a captcha, IP limits, or "suspicious account" heuristics. I need a ban on storing a review without an order.

The cost is real too: there will be few reviews. There is no public "leave a review" button for anyone who walks by. Only people who already interacted can write. That is a trade of volume for trust, and it has to be agreed with the business before you start.

Data schema

Which table the review lives in, and which other tables it must point at. The basic version is a reviews table, a translations table, and two numbers on the object being reviewed.

Two ideas without which the schema falls apart.

Two levels of object. There is a concrete experience (I bought a 💻 MacBook Pro 14" with 512 GB) and there is an entity that accumulates reputation (the 💻 MacBook Pro 14" in general). In a shop that is a SKU and a product card. In a restaurant - 🍝 a dish and the venue. For a contractor - a specific job and a person. If you collapse the levels, you either lose precision ("which salad was this review about?") or you never accumulate statistics.

Authorship on email, not on an account. A guest places an order without registering. There is no account; there is always an email. If you hang the review on user_id, half of the people simply cannot write.

FieldWhy a newcomer needs it
order_idProof. From the order we take the name, country, and the "verified purchase" badge.
item_idWhat the experience was actually about. Not an abstract product card.
product_idA copied link to the product card, so we can show the review on every variant and compute the average without an extra join.
ratingAn integer 1..5. Half-stars are a picture of the average; a person sets a whole star.
bodyMay be empty. A rating without text is still a full vote. It does not need moderation or translation, so people finish the form more often.
source_langLanguage of the original. Needed so we do not show a translate button when the storefront is already in that language, and so we do not write a second copy of the text into the translations table.
stateNot a "published" checkbox, but the room the review currently lives in. Why - the next section.
review_translationsSeparate rows, not body_et / body_en columns. Key (review_id, locale). The original does not go here: it already lives in reviews.body. The rating is not translated.

The average and the count sit right on the product. Computing them on the fly for one page is fine. On a storefront of 48 cards it is already expensive for two numbers that change once a week. We recompute when a review is published, hidden, edited, or deleted.

Soft delete. We do not wipe the row for good: a GDPR request and deleting spam are different stories, and in both cases it helps to know that the record existed. Uniqueness "one person - one SKU" must ignore deleted rows, otherwise the person cannot write again.

State is a graph, not a flag

The temptation is strong: a "published: yes/no" column. By week two, questions appear that a flag cannot answer. Was the review rejected for good, or is it waiting for a decision? Can the author edit published text? Where does the text go after an edit?

So state is a map of rooms and doors. The room says where the review is now. The door says who is allowed to move it to another room.

form with text held awaits a human, not listed rejected hidden, admin can restore published visible, in the average admin: Reject admin: Approve author edited text back to held admin: Approve star-only form goes to published, skipping held

Two non-obvious transitions that make a flag insufficient.

Editing published text sends the review back to held. Otherwise moderation is bypassed in two steps: write something harmless, wait for approval, rewrite it into anything. Changing only the stars does not change state - there is nothing to check in a number.

A review without text is published immediately. Human review is needed where there is free text. A rating is a vote, not a statement.

Try the transitions yourself

heldsaved, not on the storefront
publishedvisible to everyone, in the average
rejectedhidden, but reversible
Review with text created → held
On state names. I started with pending ("accepted, automation is still thinking"). While there is no automation, nobody is ever in that state. A dead state on the list costs more than a migration that adds it later.

How it looks on screen

The schema answers "what can we store". The screen answers "what can a person understand in three seconds". For reviews that matters more than it seems: a buyer does not read documentation, they scan the block with their eyes.

Three widths - three layouts

The same set of data. Three different ways to lay it out, because the eye works differently on a phone, a tablet, and a monitor.

Switch the width

4.6
23 reviews
5
16 4
5 3
2 1
0

The risotto arrived hot, the mushrooms were excellent. Just right for two.

✓ verified purchase
J James S. · United Kingdom · 2 months ago

On a narrow screen a left-hand star column eats the width of the text. So the stars sit on one row at the top, the summary is centered, and the text takes the full width.

In the catalog the stars sit right under the title, before the description. If there are no reviews, there are no grey stars and no "0.0" either. The row is simply missing. Otherwise the storefront looks like a graveyard of empty ratings.

Three JBL headphone cards in the Euronics catalog: stars and a review count under the title
Euronics puts the average and the count right under the title in the grid. Those are exactly the two numbers from the product card: without a precomputation, a catalog of dozens of cards has nothing to draw.

Order inside the card - by importance

People scan, they do not read. So the most important thing has to sit higher.

Anatomy of the card

1
Stars. In a fraction of a second: good or bad. On a phone, centered under the summary; on a tablet, as a ruler on the left.
2
Text, if it exists. That is the experience. We do not draw an empty "no comment" stub: the block is simply missing, and the card is shorter.
3
Trust badges. "Verified purchase", "this exact SKU". Proof comes after the quote, so it does not compete with it for attention.
4
Who and when. Name, country, relative time. The author sits at the bottom: experience first, then the person.

Long text is cut at about five lines. A click expands it. Otherwise one wall of text breaks the card grid on a monitor.

Histogram: why five bars if you already have 4.6

The average is one number. It lies. A product with a single five and a product with twenty threes and fives can produce a similar average. The bars show the shape of the distribution.

People intuitively expect a "J-curve": many fives, a few fours, almost no ones. If the bars are flat or inverted, an average of 4.6 no longer reassures. A doubtful buyer will look for the negative anyway. The only question is whether they find it on your site in one click, or leave for another site.

Euronics product page: average 5.0 from three reviews, a histogram of only fives, and a textless card with likes
Three reviews, all fives, average 5.0. Bars 1-4 are empty, there is nothing to filter, and the "perfect" picture only gets stronger. The invariant is written out loud: only a buyer can rate. Likes on a review with no text are noise: there is nothing to vote for.

Click a distribution row. The 4.6 summary does not change

4.6
23 reviews
5
16 4
5 3
2 2
0 1
0

Zero bars are not clickable: an empty list looks like a broken site. At five reviews, filters only add noise. A sensible threshold for showing the block is around ten.

Sorting

Three modes you actually need from day one. Each answers a different reader question.

SortReader questionWhy not always the default
Newest first"What are people saying now?"A good default. Freshness matters more than a perfect order.
Critical first"Where is the catch?"A perfect rating looks suspicious. One click to the ones raises trust in the whole block.
With text first"I want experience, not a vote"A rating without text is useful for the average, but in the list it is an empty card. Better not to hide it, just push it down.

A "most helpful first" sort appears only together with a Helpful button. A catalog filter by stars is a separate feature too, and it is no longer the review list on a card: it is the search results themselves.

Catalog filter by stars

On a product card a person clicks a bar to see the ones. In the catalog they want something else: "show products rated 4 stars and up" or "highest rated first". Those are different filters. One cuts reviews inside one object. The other cuts the objects themselves in a long result list.

The stars already sit on the product card. The question is not "where do we get the number", but "how do we search by it quickly" and "what do we do with thousands of items that have no reviews".

Alibaba sidebar: Store reviews filter with 4.0 and up, 4.5 and up, and exactly 5.0, with no options below 4.0
Alibaba in search cuts stores, not reviews inside a card: 4.0 and up, 4.5 and up, exactly 5.0. Twos and threes are not in the sidebar. People cut the weak, they do not collect the middling.
AliExpress results for esp32: Quality and trust filter 4 stars and up is active, no two-star option in the sidebar
AliExpress is even shorter: one "4 stars & up" checkbox and a chip above the grid. The same gesture. Nobody arrives in a catalog asking for "exactly two stars".

What actually goes in the sidebar

People pick
  • 4.0 and up
  • 4.5 and up
  • exactly 5.0
  • has reviews
They do not put in the sidebar
  • exactly 2 stars
  • exactly 3 stars
  • The 1-5 bars on a product card are for reading the negatives. In the catalog that is a different question.

The cost of a separate feature. The rating has to go into the search index, and every Approve or Reject will start reindexing the product. Items with no reviews: send them to the end, hide them at a threshold, or add a separate "has reviews" chip. The small-sample trap: one five will beat a 4.6 from 23 reviews unless "Highest rated" is corrected with a minimum N or a Bayesian average.

Load more, not a wall of text

On a phone, start with 3 reviews. On a monitor you can open more. A "more" button loads the next batch without reloading the page. You can start loading the next batch in advance, while the person is still reading the first - then the click feels instant.

Why: a product page is heavy even without reviews. A hundred reviews at once will slow both the first paint and the search markup (only reviews that are actually rendered go into it).

Relative time

We write "2 months ago" and hide the exact date in a hover tooltip. People compare the experience with "now", not with a calendar. A 2019 review of a 2024 model reads differently from "2 months ago".

The "verified purchase" badge

This is not decoration. It is the schema invariant translated into human language. If a review cannot be created without an order, we have the right to say that out loud. Without the badge the block looks like ordinary comments that anyone could have faked.

Name, country, and why there are no flags

Publicly: first name plus the first letter of the last name ("James S."), country in words, date. Not the full name, not the city, not the email.

I deliberately do not put a country flag. Three reasons:

  • In a small country, a name + a rare order + a flag already identifies the person. GDPR asks you to minimize.
  • A flag reads as politics, not as "where the order came from".
  • The word "Estonia" is clear to a screen reader and in black-and-white print. A flag icon is not.

We do not show the city for the same minimization reason. The country is enough to understand the context ("do people from my market write here").

One review, several models. The MacBook analogy

Imagine Apple. MacBook Pro 14" is the product card. Space Gray 512 GB and Silver 1 TB are variants. The person bought 512 GB. Their experience is almost entirely about the same laptop: screen, keyboard, heat. But battery and weight are already different.

So the review is written against the purchased variant, and shown on every variant of the same card. And it must be marked: "this is about 512 GB" if you are currently looking at the 1 TB page.

The same review on two pages

Purchased page: MacBook 14" · 512 GB

Quiet, the screen is excellent. By evening the battery lasts just about a workday.

✓ verified purchase ✓ this exact model · 512 GB
Neighboring page: MacBook 14" · 1 TB

Quiet, the screen is excellent. By evening the battery lasts just about a workday.

✓ verified purchase a different model was ordered · 512 GB →

The same in a restaurant: a review of "Caesar with chicken" is visible on the "Caesar with shrimp" page, but the badge says "this person ordered chicken". For a worker: a review of a bathroom renovation is visible on the profile, but marked as "bathroom, not kitchen". The aggregate (average for the card) is counted across all variants together. Otherwise each SKU would have one review, and the statistics would never add up.

How to get people to write reviews at all

The form can be perfect. Without an email after delivery almost nobody will open it. People do not wake up wanting to write a review. They need a reminder at the moment when the experience already exists, but has not been forgotten yet.

≈ 14 daysFirst email: "how did it go / how did it taste?". The button leads into the order, and from there into the form.
≈ 28 daysOne reminder if the item still has no review. There is no third email in this series.
≈ 6-12 monthsNo longer "leave a review", but "how has the thing held up". A separate feature, but you have to think about it in the schema early.

The first email

Subject: How did your order work out?

Hi Mari,

Order #R123456789 arrived two weeks ago. How is the drill? Your rating helps other people choose.

Leave a rating

Do not want these emails? Unsubscribe

Important details. The email does not ask for five stars, it asks for experience. Unsubscribing is a separate confirmation, not a link that silently changes a setting via GET (mail robots open those on their own). The form lives on a separate page: the order line has room only for a button.

The form and moderation

Without this block, reviews do not live: a person has to be able to write, and a person from support has to decide the fate of the text.

How a person reaches the form

Two entry points, one controller. Either the email after delivery leads into the order, and from there into the form for that item. Or the person opens the page of a purchased product (or a neighboring variant) themselves and sees the form, because they already have a matching order.

Buyer Email Site order or product Review form POST /reviews from email ≈ 14 days · link to order opened the order "rate" button from product page opened purchased item same controller stars, optional text text → held and email to support · no text → published now

There is no public "for everyone" button. The link from the email does not create a review: it only opens the order. The form lives on a separate page - the order line has room only for a button.

The form

There is one required field: the stars. Text is optional.

A queue plus an email

The admin UI is a place people visit when they are already sitting at a computer. A review arrives at any time. So every text review in held sends an email to support, and the queue remains the source of truth.

The order number in the email subject pays for itself immediately: helpdesk attaches the thread to the purchase on its own. The admin does not edit the text. Only Approve or Reject. The moment a moderator rewrites someone else's words, reviews stop being reviews.

Markup for search engines

A search engine reads more than what a person sees. schema.org has two types for this inside a product card:

  • AggregateRating - the average and the review count. This is where the stars in search results come from.
  • Review - one visible review: author, date, rating, text.

A separate JSON-LD is not needed if the page already has a Product: both blocks nest inside it. In short it looks like this:

<article itemscope itemtype="https://schema.org/Product">
  <h1 itemprop="name">18V drill/driver</h1>

  <div itemprop="aggregateRating" itemscope
       itemtype="https://schema.org/AggregateRating">
    <span itemprop="ratingValue">4.6</span>
    <span itemprop="reviewCount">23</span>
  </div>

  <article itemprop="review" itemscope
           itemtype="https://schema.org/Review">
    <span itemprop="author">James S.</span>
    <time itemprop="datePublished">2026-07-02</time>
    <span itemprop="reviewRating" itemscope
          itemtype="https://schema.org/Rating">
      <span itemprop="ratingValue">5</span>
    </span>
    <p itemprop="reviewBody">Holds torque evenly.</p>
  </article>
</article>

Only published reviews that are actually rendered go into the markup. So a "1 star" filter cannot be a page URL change: otherwise Google would see an average of 4.6 plus only the bad texts. The filter lives inside a page fragment and does not touch the canonical address.

Google results for tesla model y car reviews: each result has stars, an author, and a pros and cons block
AggregateRating exists for those stars in search results. Here Google assembled other people's Tesla Model Y reviews: rating, author, pros and cons. For a shop the same thing is taken from the average and count on the card, not from a picture. An editorial review and a user review look similar to a search engine if the markup is in place.

Automatic translation and auto-flagging

People write in their own language. On a foreign storefront that review is noise. Manual moderation does not scale: a human reads every text to catch rare spam and a phone number in a paragraph.

One model solves both jobs in one call. It reads the original anyway to understand the language. In the same pass it checks the text (personal data, spam, off-topic complaint) and prepares translations.

We do not translate "into every language in the world", but into a fixed set of locales the storefront is built for. If the site speaks Estonian, English, Russian, and Finnish - the model writes those rows into review_translations in one pass. The original language is not in the set: it already lives in reviews.body and source_lang.

Another architecture is to translate into the viewer's language on demand and immediately save the result into the same table. The first person with a Japanese Accept-Language waits a second; the next ones read the cache. That is cheaper if there are many locales, or one storefront with a long tail of languages. It is worse if the moderator must ship already-ready text: the translation may appear after Approve, and the original will flash on the storefront for a moment.

ApproachWhen it winsCost
Storefront languages in advanceTwo to five locales, moderation before publish, instant language switchingWe pay for languages nobody will open
On demand for the viewer's languageMany locales or an unpredictable Accept-LanguageThe first viewer waits; the translation may miss Approve
Preparation is not publication. The original and the translations are saved during the check, but until Approve the storefront is empty. Approve only releases already-ready rows. Publication is instant; the moderator reads the original, not a retelling. For the "on demand" scheme, Approve releases the original, and the translation row is appended later.

This is where the pending room appears: "accepted, automation is thinking". Now someone is actually in it - for a few seconds.

On screen there is always one text, as at Airbnb. The button does not open a second block, it replaces the paragraph. If the review language matches the storefront language, there is no button.

Amazon, on a huge catalog, takes the second path even further: the original stays on screen, the translation is not swapped in by itself. On an English storefront a German review stays German, and "Translate review to English" loads the translation on click. That is cheaper when there are many languages. And the click itself is a signal: the person read far enough into a foreign text to want to understand it. That request can go into the same bucket as "Helpful" - the review was valuable enough to cross a language barrier.

Amazon reviews page: 4.7 average, a Reviews with images strip, and a German review with a Translate review to English link
Amazon keeps the original by default and loads the translation on demand. On the same screen photos are lifted out of the cards into a "Reviews with images" strip above the list: the eye sees live product shots first, then the texts. The rating does not change with the order. A "translate" click is also a metric: the review is interesting enough to read in a language that is not yours.
Euronics review list: Estonian text with a translate button, a stars-only review, and a short review in Russian
The same shop on a product page. Estonian text with "Show translation", a review with no paragraph (stars only, and that is fine), the author sometimes just "Euronics customer". I deliberately have no public "Leave a review" button: you can only write from an order.

Translation toggle

The risotto arrived hot, the mushrooms were excellent. Just right for two.

M Mari K. Estonia

Why a single 1-5 scale is not enough

"Four stars" is not meaning. It is compression. One person said that because the risotto was excellent and they waited a long time. Another - because the drill is average, but it arrived fast. A third rated the waiter, not the dish. The reader cannot see any of that.

A flat scale works well as a vote and as a number for Google. As an explanation of experience - badly. That is why on screen the text sits above the author, and the histogram matters more than the average: they at least show the spread.

Several axes help - and still constrain

The next temptation: ask for six stars instead of one. In a restaurant - taste, portion, wait, price, service, atmosphere. For a tool - torque, comfort, battery, noise, case, price. On screen that becomes a radar. The buyer sees not "4.3", but "food is excellent, wait was long".

A plus for the company: a systematically low "wait" is a complaint about the shift, not the kitchen. Axes are filled with clicks; they do not need translation or moderation.

The cost: six questions instead of one kill reviews that are already rare. Google still takes one number. If the axes are optional, one will have 3 votes and another 20 - the radar starts lying, and you have to hide an axis until a threshold.

Taste 4.8 Portion 4.4 Service 4.6 Price 4.1 Wait 3.6 Atmosphere 4.2
Airbnb reviews modal: average 4.77, a histogram, six categories such as cleanliness and check-in, topic tags, and a review translation
Airbnb keeps six axes next to the 4.77 average and the histogram. The reader sees not "almost five", but "cleanliness 4.6, check-in 4.9". "Mentioned in reviews" tags are a summary of topics from the texts, without a separate chat. The "How the review system works" link, translation, and the author's tenure on the platform - the same questions, a different object: a night in an apartment, not a drill.

Axes are useful as a catalog of topics. They do not replace a live story. A person will still write what you did not anticipate: "the bits in the kit are fine, but the latch on the case still would not open on the first try". A scale will not catch that.

AI reviews as an answer to both limits

People write badly into an empty field. You get "great" or a complaint about delivery. Several axes fix that only partly, at the cost of friction. A stronger move is a short dialogue.

A dialogue instead of an empty field

Assistant. Briefly about the drill itself, not delivery. What did you buy it for?
To assemble a kitchen, then shelves in the garage
Assistant. How is the torque and the bits? Do they strip?
Torque is even, bits are fine. The case latch is stiff

The bot assembles a draft. The person edits and confirms. They still set the stars themselves. They can close the chat and leave only a rating. Then the usual path: text into held, a human Approve.

When there are many reviews, a second AI layer is a "what they praise / what they complain about" summary above the list. Not a replacement for reviews, a table of contents. The threshold is about 20 texts: on five reviews one loud minus looks like everyone's opinion.

What they praise
  • Even torque, bits do not strip
  • Honest portions, fresh mushrooms
What they complain about
  • The case latch is stiff
  • Waited forty minutes

Risks: the same "shop voice", a legal note "assembled with an assistant", hallucinations in the summary. The stars remain a fact from the database. If the model is wrong, the average is still honest.

Author context

For a restaurant the main question is the occasion: a business lunch and a birthday read differently. For a tool - the job: assembled a shelf or drove a frame. For a contractor - the type of object. "It fit perfectly" is useless if the author had a different context.

Glassdoor review cards: job title, current or former employee, pros and cons, Helpful button
For an employer, author context matters more than bare stars: job title, current or former, city, pros and cons as separate fields. Helpful is already on the card. A job title filter is a family, not an exact match: otherwise with 404 reviews the list is always empty.

The data is often already in the order. One column on the review, a filter by family, not by exact match: otherwise the filter is always empty. An exact address and a license plate never go into the public block.

The "for my car" filter, and the same pattern elsewhere

Some businesses sell a part that has to fit something the buyer already owns. The main question is not "is this a good thing", but "will it fit mine". A review that says "it fit perfectly" is useless if the author had a different host.

Spare parts are the cleanest example. A person is not looking for brake pads "in general", they are looking for pads for their Octavia. They already picked the car in the fitment selector; it sits on the order. The form should not ask them to type it: we prefill it from the order and let them confirm or clear it. We store the catalog name, not free text. A license plate is never shown.

Three degrees of match, not yes or no

All reviews · 23 My model: Octavia III · 7 Exactly my 1.6 TDI · 1 With text only · 19

Fitted in about an hour, no modifications. Bite is much better than the cheap set I had before.

✓ verified purchase Škoda Octavia III 1.6 TDI · 2014
J James S. · the exact same variant

Went on without any trouble, though the pedal feels softer than the originals.

✓ verified purchase Škoda Octavia III 2.0 TSI · 2017
A Antti T. · same family, different engine

Good pads for the money. They dust the wheels more than I expected in the first weeks.

✓ verified purchase
D David W. · no car given, so no badge

An exact "same engine as mine" filter is almost always empty at the start. Cut by model family, and print the variant on the badge: the reader can see that the author has a 1.6 TDI and they have a 2.0 TSI.

The same pattern lives far beyond car parts. Anywhere a SKU fits a catalog of hosts the buyer already owns, a review has to be read through that filter.

BusinessWhat the buyer already ownsExact matchFamily we filter by
Auto partsThe car in the garageA specific engine and yearsModel: Octavia III
Cases and glassThe phoneiPhone 16 ProiPhone 16
CartridgesThe printerHP LaserJet Pro M404dnM404 series
Lenses and batteriesThe cameraSony A7 IVSony E mount
Bike partsFrame and groupsetSize 56 / UltegraRoad bike
Watch strapsThe watchA specific model, 20 mm20 mm lug width
PluginsThe site stackWordPress 6.6 + WooWordPress
HVAC filtersThe unitThe exact installer SKUManufacturer series

One rule covers all of them. An exact filter is almost always empty. A family gives a useful list. We do not store free text ("for my car", "for my printer"): the compatibility catalog already has the name, and it does not need translation or moderation. If the host on the order is unknown, we do not render the field in the form at all.

The reader's car, phone, or printer is used only for the filter; it is not written into someone else's review. The author may have looked at three hosts and bought a part for a fourth, so the form asks them to confirm instead of silently prefilling.

An update after 6-12 months

After two weeks a person will say "it arrived fast". After a year they know the main thing: it held up or it did not. A competitor who asks on day two will never have that.

The first review and a later edit of the experience

Assembled the kitchen over the weekend. Torque is even, bits do not strip.

M Mari K.

Technically it is better to have a separate linked row pointing at the parent, not fields inside the same review: the update goes through the same moderation and translation. In the average rating the thread counts as one vote; we take the latest score. Otherwise one person votes twice. That is exactly the kind of decision that is expensive to change after launch.

Photos and video

The most convincing format and the heaviest: file intake, resize, previews, antivirus, image moderation, and for video also transcoding. Plus consent to publish and deleting every copy under GDPR.

If media do appear, they should sit above bare text in the list: the eye catches a photo harder than a date. The object's rating does not change because of list order. Amazon solves this with a strip above the list, not by reshuffling cards: a separate gallery of reviews with photos gives the same media priority, only even earlier, before the first paragraph.

Media reviews first, the photo expands in place

Photos and video first
Inside the group - newest first. The rating does not change.

The risotto arrived hot, the mushrooms were excellent. Attaching a photo of the plate and a short video.

✓ verified purchase
Close-up of a plate of mushroom risotto
Mushroom risotto on a restaurant table
video 0:14 ▶
J James S. · 2 months ago

Holds torque evenly. The case is convenient, the latch is stiff.

✓ verified purchase
M Marek T. · 1 month ago

Marek's review is newer, but it sits second: media outweighs the date.

Close-up of a plate of mushroom risotto
Mushroom risotto on a restaurant table
video 0:14 ▶
Close-up of a plate of mushroom risotto
1 of 3 · uploaded by a guest

Why not a modal: the review text stays on screen, and the picture is read together with what the person wrote about it. A separate window breaks reading and breaks Back on a phone.

Helpful and the author hub

Other readers' votes give you a "most helpful first" sort and a reason to write with more substance. The cost: a new votes table and protection against gaming. The "Helpful" label matters more than a like: the person is judging the usefulness of the experience, not agreeing with the stars. The vote does not change the object's rating.

The button on the card and a personal section

The risotto arrived hot, the mushrooms were excellent. Just right for two. If you come as a pair - get a salad too.

✓ verified purchase
one vote, a second click undoes it
M Mari K. · Estonia · 2 months ago
M
Your reviews help people choose
The score grows when others mark the text as helpful.
84
12reviews
47helpful
4.2per review
plate
Mushroom risotto
2 months ago · published

Hot, the mushrooms were excellent. Just right for two.

♡ Helpful · 18
case
18V drill/driver
3 weeks ago · under review

Torque is even, bits are fine. The latch is stiff.

⏳ under review

Only a signed-in user can vote, you cannot vote on your own review, one active vote per "person + review" pair. A guest sees the counter, but a click asks for sign-in and does not bump the number in advance. Otherwise one browser becomes hundreds of likes by clearing cookies.

Expert status and careful rewards

Once Helpful is already working, the temptation is to highlight authors whose texts reliably help people choose. A badge next to the name says: this person writes more than once, and people read them. A discount can add motivation. It can also turn reviews into paid endorsements if you tie it to a five-star score.

The public signal and the reward in the account

Fitted without modifications. After 5,000 km there is no vibration, and they make less dust than my previous pads.

✓ verified purchase
M Mari K. ◆ expert · Estonia · 2 months ago

16 points to expert status

A sample threshold: at least 5 published reviews with substance, 25 helpful votes from different people, and no recent violations.

Reward for contribution: 5% off the next order

30 days, once, not stacked with other promo codes. This is an experiment mechanic, not a day-one promise.

The badge means author experience, not a mechanic certificate. You cannot give "expert" for a purchase count or one popular review. You need several published texts, a stable Helpful rate, and a minimum of independent voters. Otherwise the badge is bought with one lucky card.
Do not pay for positivity. Points and the discount depend on usefulness and specificity of the text, never on 5 stars. Sharp, precise negatives must have the same chance. Otherwise people will inflate scores for a perk, and trust in the review block will collapse. That is why the policy page cannot say "we pay for reviews". If a discount appears, it pays for contribution, not for a rating.

A safe launch order. First the author hub: the person sees their reviews and the next step. Then Helpful: check whether people vote at all. Then the badge on a small group: compare usefulness and conversion, not only clicks. The discount is last, a limited A/B, and only after anti-gaming and a discount-economics check. You cannot start with the discount: that is an immediate financial reason to write anything.

Avatars

A live face next to a review makes it look like a person's words, not a row in a database. Three sources by priority, none required: your own photo in the account, Gravatar from an email hash, a letter in a circle as a fallback.

The same slot: a live photo and a fallback letter

The risotto arrived hot, the mushrooms were excellent. Just right for two.

✓ verified purchase
James S. · Estonia · 2 months ago

Holds torque evenly, bits do not strip.

✓ verified purchase
Mari K. · Finland · 3 months ago

Tasty, but we waited forty minutes.

✓ verified purchase
A Aino V. · Sweden · 3 months ago

Gravatar is a request from the reader's browser to a third party: it sees the email hash and the IP. Without explicit consent and a line in the privacy policy that is not allowed. Coverage in ordinary shops is low, so the main effect comes only from your own upload. You cannot publish a personal photo without moderation: someone else's face, or anything else, will end up on the storefront.

A public reply and brand reputation

A short reply under a negative review takes the sting out for every future reader, not only for one complainant. Technically simple. Organizationally hard: who replies, in what time, in which language, and whether an email goes to the author.

The reply is nested under the review; it is not a second review

The portion arrived lukewarm, the salad went soggy. The risotto itself was fine, but the evening was already ruined.

✓ verified visit
A Aino V. · 3 months ago
Kitchen reply · 3 months ago

Sorry about the wait and the lukewarm salad. We changed the shift for this slot and sent you a voucher for a later visit. If anything else is wrong - reply to the order email.

Rules without which a reply hurts: do not reply only to ones, do not argue with the author, do not flash the order number, one reply per review. We take the dialogue into support and leave the outcome in public.

An aggregate across all products of a brand (or all of an employee's shifts) turns a handmade "premium" badge into a provable number. You need protection against a tiny sample: one five should not lift a brand above someone with a thousand honest 4.6s. And you cannot hide weak categories inside a pretty average.

The "how we handle reviews" page

In the EU, if you show reviews, you are probably required to explain: do you check that real buyers left them, and how. The good news: with a "review must point at an order" schema this page is written in ten minutes and sounds convincing. The link is a calm footnote next to the block title, not a "you can trust us" banner.

Glassdoor summary for Bolt Tallinn: a disclaimer that the company cannot delete reviews, a 3.3 rating, CEO approval, and a six-month chart
Glassdoor puts trust in the header: the company cannot edit or delete reviews. Then not one number, but a pack: 3.3, "would recommend to a friend", CEO approval, a business outlook, and a six-month chart. For a brand or an employee this is closer than the average on one product card. The "see how Glassdoor protects users" link is the same footnote about the rules, only louder.

The footnote by the block, and the page itself

4.6
23 reviews
How we handle reviews
  • Only someone who already ordered, ate, or closed a shift can write. There is no public "for everyone" button.
  • We do not pay for a review and do not ask for five stars.
  • We do not delete negatives. We hide only spam, personal data, and off-topic text.

Privacy: the only irreversible action

A review is a place where a private action becomes public. Two decisions have to be made explicitly.

What to show. First name plus the last-name initial, country in words, a relative date. Everything else from the order stays inside.

What remains after an account is deleted. If reviews disappear with the account, the product's average rating is rewritten after the fact. If they stay, they have to be anonymized. Both options are defensible. You have to choose before launch and write it on the account-deletion screen, not only in the policy.

The deletion screen and the same review after it

!

Delete account

Your data will be deleted. Published reviews will stay on the site - without a name, only initials and a country.

If you need a specific review taken down, delete it before deleting the account, or write to us.

Sobis autole täpselt, tarne võttis kolm päeva.

✓ verified purchase
? Customer · Estonia · 3 months ago

user_id is nulled, the author is anonymized, the text and rating stay. The product rating is not rewritten after the fact. The right to delete a specific review still exists: the author before account deletion, or an admin on request.

What I took away from the design

What I took away for myself is a useful example of a vertical feature that is not obvious from the outside. Reviews look like a table and stars. Inside they are several layers at once, and you have to design them together, not as a pile of tickets.

I had to think through the basics: who is allowed to write, what lives in the schema, which rooms a review occupies. I had to study competitors - not to copy a screen, but to see which questions they had already closed and which they had walked around. I had to design UI/UX for different widths and different states: phone and monitor, a card with text and without, the storefront language and a foreign paragraph, media and a bare rating.

Separately - who buys, and how that person adds a review. There is no public "for everyone" button. The person arrives from an email or from a purchased product page. So the form, the email, and moderation are one feature, not three backlogs.

Then priority. Features go into the queue by weight and importance, not by how pretty they look. The invariant "a review without an order does not exist" is cheap and closes nine tenths of future problems. Photos, Helpful, avatars, and an expert badge are convincing, but each one pulls in its own table, antifraud, and moderation. A contribution discount cannot launch before anti-gaming, and it cannot be tied to a five-star score. A catalog filter by stars and a "for my X" filter look like sidebar checkboxes, but they need an index and a careful family, not an exact match. Those can wait. What is expensive to migrate - a year-later update, and the fate of a review when an account is deleted - has to be decided before launch.

I am not even going into the email details here. Do you mail everyone who ever bought? How do you let people unsubscribe and subscribe again? How do you keep this apart from ordinary transactional mail? Maybe you should not spam everyone, and not write on every purchase. The same for moderation: which rules, what the rejection process looks like, what the author sees when the text is not released. Those are separate products inside the same vertical. For a start, the invariant, three state rooms, and a human who presses Approve or Reject are enough.