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.