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.
| Argument | Type | Required | Description |
|---|---|---|---|
productId | Int! | ✅ Yes | ID of the product being reviewed. The product must exist, otherwise the mutation fails. |
title | String! | ✅ Yes | Review headline. Rejected when empty. |
comment | String! | ✅ Yes | Review body text. Rejected when empty. |
rating | Int! | ✅ Yes | Star rating. Must be 1 to 5; anything outside that range is rejected. |
name | String! | ✅ Yes | Reviewer's display name, as it appears on the review. |
email | String | ❌ No | Accepted by the schema but not stored on the review. |
status | Int | ❌ No | Leave unset. New reviews are created as pending for moderation — see Review Status. |
attachments | String | ❌ No | JSON string holding an array of Base64 data URIs — see Attachments Format. |
clientMutationId | String | ❌ No | Arbitrary 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
| Field | Type | Description |
|---|---|---|
productReview | createProductReviewPayloadData | The review that was created. |
productReview.id | ID! | IRI-style review identifier. |
productReview._id | Int! | Numeric review ID. |
productReview.name | String! | Reviewer's name. |
productReview.title | String! | Review title. |
productReview.rating | Int! | Star rating, 1 to 5. |
productReview.comment | String | Review body text. |
productReview.status | String! | Approval status — "pending" on a newly created review. |
productReview.attachments | String | JSON string of the stored attachments, each with a type and a url. null when none were uploaded. |
productReview.createdAt | String | ISO 8601 creation timestamp. |
productReview.updatedAt | String | ISO 8601 timestamp of the last change. |
clientMutationId | String | The 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/orvideo/; 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:
"[\"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) andurl(file URL)
Example Response:
"[{\"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:
| Status | Description |
|---|---|
"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.
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:
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
- Validate before submitting — check
ratingis 1 to 5 and thattitleandcommentare non-empty, so the customer sees a field-level message instead of a failed mutation - Keep each attachment under 5 MB — that is the hard server limit, and one oversized file fails the entire submission
- Compress images first — WebP keeps a photo well inside the limit and uploads faster over a mobile connection
- 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
- Never send
status— reviews are createdpendingfor moderation, and the field cannot promote one to approved - Tell the customer the review is pending — it does not appear on the product page until an admin approves it
- 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
| Scenario | Cause |
|---|---|
| Missing input | The input argument was omitted, or a required field inside it is absent. GraphQL rejects the document before the mutation runs. |
| Product not found | No product exists for the supplied productId. |
| Rating out of range | rating is below 1 or above 5. |
| Empty title or comment | Either field was sent as an empty string. |
| Invalid attachment | The data URI is malformed, the Base64 will not decode, or a decoded file exceeds 5 MB. |
| Reviews disabled | The store has customer reviews switched off, or the request is unauthenticated and guest reviews are not allowed. |
Related Resources
- Get Product Reviews - Query product reviews
- Get Product - Query product details
- Shop API Overview - Overview of Shop API resources

