Skip to content

Create Product Review

About

The createProductReview mutation allows customers to submit product reviews with ratings, comments, and media attachments. Use this mutation to:

  • Submit product reviews from customers
  • Add images and videos as review attachments
  • Set review status (pending, approved, disapproved)
  • Track review submissions with client mutation ID
  • Enable customer feedback on products
  • Build user-generated content on storefront
  • Collect product ratings and reviews

This mutation supports Base64-encoded image and video attachments for rich media reviews.

Arguments

All input fields live inside the mutation's input object.

ArgumentTypeRequiredDescription
productIdInt!✅ YesID of the product being reviewed. The product must exist, otherwise the mutation fails.
titleString!✅ YesReview headline. Rejected when empty.
commentString!✅ YesReview body text. Rejected when empty.
ratingInt!✅ YesStar rating. Must be 1 to 5; anything outside that range is rejected.
nameString!✅ YesReviewer's display name, as it appears on the review.
emailString❌ NoAccepted by the schema but not stored on the review.
statusInt❌ NoLeave unset. New reviews are created as pending for moderation — see Review Status.
attachmentsString❌ NoJSON string holding an array of Base64 data URIs — see Attachments Format.
clientMutationIdString❌ NoArbitrary string echoed back in the payload, for correlating a response with its request.

The review is attributed to the authenticated customer when a Bearer token is sent. Without one it is stored as a guest review, which the store must allow — reviews are rejected outright when customer reviews are switched off, and guest reviews are rejected separately when the store requires a login to review.

Possible Returns

FieldTypeDescription
productReviewcreateProductReviewPayloadDataThe review that was created.
productReview.idID!IRI-style review identifier.
productReview._idInt!Numeric review ID.
productReview.nameString!Reviewer's name.
productReview.titleString!Review title.
productReview.ratingInt!Star rating, 1 to 5.
productReview.commentStringReview body text.
productReview.statusString!Approval status — "pending" on a newly created review.
productReview.attachmentsStringJSON string of the stored attachments, each with a type and a url. null when none were uploaded.
productReview.createdAtStringISO 8601 creation timestamp.
productReview.updatedAtStringISO 8601 timestamp of the last change.
clientMutationIdStringThe clientMutationId sent with the request, echoed back.

Attachments Format

Input Format (Creating Review)

  • Must be a JSON string containing an array — not a GraphQL list
  • Each item is a Base64-encoded data URI in the form data:{MIME_TYPE};base64,{BASE64_DATA}
  • The MIME type must start with image/ or video/; the subtype is taken from whatever you send and becomes the stored file's extension
  • Each decoded file must be 5 MB or smaller. A larger one is rejected and the whole mutation fails
  • A malformed data URI, or Base64 that will not decode, is rejected the same way

Example Input:

json
"[\"data:image/webp;base64,iVBORw0KG...\", \"data:image/png;base64,iVBORw0KG...\"]"

Response Format (Retrieved Review)

  • Returned as a JSON string containing an array of objects
  • Each object has type (image/video) and url (file URL)

Example Response:

json
"[{\"type\":\"image\",\"url\":\"https://api-demo.bagisto.com/storage/review/94/photo1.webp\"},{\"type\":\"video\",\"url\":\"https://api-demo.bagisto.com/storage/review/94/demo.mp4\"}]"

Review Status

A review's status is one of three strings, and a newly created review is always pending:

StatusDescription
"pending"Awaiting moderation. Not shown on the storefront.
"approved"Published and visible on the product page.
"disapproved"Declined and never published.

Moving a review between these states is an admin action, not something the storefront does. The status input field takes an integer and is written through unchanged, so a value passed there does not map onto any of the three states and leaves the review in a status no query can match — leave it unset.

Use Cases

1. Review form on a product page

The minimum submission is productId, title, comment, rating, and name. Send the shopper's Bearer token with the request and the review is attributed to their customer account; without one it is stored as a guest review, which the store must be configured to accept.

graphql
mutation submitReview($input: createProductReviewInput!) {
  createProductReview(input: $input) {
    productReview {
      _id
      status
      createdAt
    }
  }
}

Reading status back confirms the review landed as pending.

2. Attaching photos from a file input

Attachments are not file uploads. Each file is read into a Base64 data URI, the URIs go into an array, and that array is serialised to a string before it is sent:

js
const toDataUri = file => new Promise(resolve => {
  const reader = new FileReader()
  reader.onload = () => resolve(reader.result)
  reader.readAsDataURL(file)
})

const uris = await Promise.all([...fileInput.files].map(toDataUri))

input.attachments = JSON.stringify(uris)

Check each file against the 5 MB limit before encoding — Base64 inflates the payload by roughly a third, and one oversized file fails the whole submission.

3. Telling the shopper what happens next

The review does not appear on the product page when the form is submitted, because it is created pending. Show a confirmation that it is awaiting approval, and read it back with Get Product Reviews using status: "pending" and the same productId if the page needs to display it.

4. Correlating a response with its request

Pass a clientMutationId and it is echoed back in the payload, which lets a client match a response to the submission that produced it — useful when a review form is retried or several submissions are in flight.

Best Practices

  1. Validate before submitting — check rating is 1 to 5 and that title and comment are non-empty, so the customer sees a field-level message instead of a failed mutation
  2. Keep each attachment under 5 MB — that is the hard server limit, and one oversized file fails the entire submission
  3. Compress images first — WebP keeps a photo well inside the limit and uploads faster over a mobile connection
  4. Cap the attachment count client-side — the server sets no limit, and every file is Base64-encoded into the request body, so a handful of photos is already a large payload
  5. Never send status — reviews are created pending for moderation, and the field cannot promote one to approved
  6. Tell the customer the review is pending — it does not appear on the product page until an admin approves it
  7. Send the customer's Bearer token when there is one — it attributes the review to their account; without it the review is stored as a guest submission and is rejected outright if the store does not accept those

Error Scenarios

ScenarioCause
Missing inputThe input argument was omitted, or a required field inside it is absent. GraphQL rejects the document before the mutation runs.
Product not foundNo product exists for the supplied productId.
Rating out of rangerating is below 1 or above 5.
Empty title or commentEither field was sent as an empty string.
Invalid attachmentThe data URI is malformed, the Base64 will not decode, or a decoded file exceeds 5 MB.
Reviews disabledThe store has customer reviews switched off, or the request is unauthenticated and guest reviews are not allowed.

Released under the MIT License.