The standard product review metaobject is a restricted definition available to approved product review apps. This guide covers implementation requirements, the approval process, and how to syndicate reviews using the standard definition. Any partner can participate in the program by meeting the requirements and signing the review-specific amendment. ## Applying for the standard product review syndication program The standard product review metaobject allows review apps to store and manage product reviews in a consistent format across Shopify. When you syndicate reviews to this metaobject, they become eligible for display in the Shop app (subject to [eligibility requirements](https://help.shopify.com/manual/online-sales-channels/shop/product-reviews/sync-partner-apps#eligibility)). To implement the standard product review metaobject in your app, follow these steps: > Note: > If you join the standard product review syndication program, all valid reviews must be syndicated to product review metaobjects and all total review counts and average star ratings must be syndicated to product review metafields. 1. **Request test access through Partner Dashboard** - From your development app, go to Partners Internal - Navigate to API Access - Click on the card to request access to the scope for standard product reviews - This will enable the scopes on your dev test store only - The Shop app channel will be enabled for your dev shop so you can test functionality in the Shop app 2. **Test your implementation** - Implement the review syndication with your dev app - Test the implementation thoroughly in your dev store - Verify functionality in the Shop app 3. **Submit for review** - Submit your app for review - If you already have a production app you want to opt in, include that production app ID in your review request - The app review team will review your implementation - If approved, you'll need to sign an updated agreement - After signing: - Your dev app will either be promoted to a live app with the necessary scopes, OR - If you included a production app ID in your review request, that app will be granted the necessary scopes ## Requirements for the standard product review syndication program Apps must meet the following requirements before being accepted into the standard product review syndication program: ### Requirement 1: review standard metaobject syndication All reviews must be synchronized as standard metaobject entries. Reviews must be properly validated according to the [metaobject field requirements](#product-review-metaobject-fields): - Rating field must be a valid JSON object matching the schema: `{"scale_min":"1.0","scale_max":"5.0","value":"5.0"}`. - Required fields must be populated: `rating`, `submitted_at`, `source`, `product`, and `app_verification_status`. - Reference fields (`author`, `order`, `product`, `product_variant`) must reference valid resources in the merchant's store. - Language field must use valid ISO 639-1 standard language codes. ### Requirement 2: aggregate count updates Apps must maintain accurate aggregate review data by updating the following [standard metafields](/docs/apps/build/custom-data/metafields/list-of-standard-definitions) on each product: 1. Product Rating (`reviews.rating`): The average star rating for the product 2. Product Rating Count (`reviews.rating_count`): The total number of reviews for the product These metafields must be kept in sync with the metaobject reviews and updated whenever: - A new review is created - An existing review is updated - A review is deleted - A review's published status changes ### Additional Implementation Requirements When [importing/exporting reviews via CSV](#importing-and-exporting-reviews-with-handles): - Reviews with existing metaobject handles must be sourced from the metaobjects API. - Reviews must be enriched with data from CSV imports to support all merchant imported fields. - Metaobject webhook events must be handled for all merchants with the app installed. For [bulk operations](#getting-multiple-review-metaobjects-in-bulk): - JSONL files must not exceed 20MB. - Consider partitioning JSONL files into batches of 10,000 reviews. - Handle validation errors and retry logic for failed operations. For review publication status: - Published reviews must have publishable status set to `ACTIVE` and a valid `published_at` timestamp. - Unpublished reviews must have publishable status set to `DRAFT` and `published_at` set to `null`. When [handling webhooks](#webhook-subscription): - Subscribe to all metaobject topics: `METAOBJECTS_CREATE`, `METAOBJECTS_UPDATE`, `METAOBJECTS_DELETE`. - Include the `subTopic` field in the webhook subscription payload. - Handle webhook requests asynchronously to prevent API throttling. - Implement retry/backoff strategy for API requests. For review syndication: - All valid reviews must be syndicated to metaobjects. - Reviews must be eligible for display in the Shop app (subject to eligibility requirements). - Maintain proper attribution through the `createdByAppId` field. - Handle cases where imported reviews may not have correct order, product, or customer references. ### Verified by Shop Branding Requirements Apps that participate in the standard product review syndication program must display the "Verified by Shop" badge to indicate that their reviews are eligible for display in the Shop app. This helps merchants and customers identify trusted review sources. #### Badge Requirements 1. **Badge Assets** - Download the official "Verified by Shop" badge assets. There are [purple](https://cdn.shopify.com/static/shop/Verified%20by%20Shop_Purple_EN.svg), [black](https://cdn.shopify.com/static/shop/Verified%20by%20Shop_Black_EN.svg), and [gray](https://cdn.shopify.com/static/shop/Verified%20by%20Shop_Gray_EN.svg) options. - Use only the official badge assets provided by Shopify - Do not modify, recreate, or alter the badge in any way 2. **Badge Placement** - Display the badge prominently in your app's review interface - Include the badge on any review widgets or components that display syndicated reviews - Ensure the badge is visible when reviews are displayed on the merchant's storefront 3. **Badge Usage** - Use the badge only for reviews that are successfully syndicated to or from the standard product review metaobject - Do not use the badge for reviews that are not eligible for Shop app display - Ensure the badge is properly sized and legible on all devices and screen sizes For questions about badge usage or to request access to the badge assets, contact the Shop team through the Partner Dashboard. ## Product review metaobject fields The following table details all available fields for the product review metaobject: | Field name | Field key | Field type | Required | Validation | |------------|-----------|------------|----------|------------| | Rating | `rating` | Rating field | Yes | A validated JSON object matching the following schema: `{"scale_min":"1.0","scale_max":"5.0","value":"5.0"}` | | Title | `title` | Single line text field | No | None | | Body | `body` | Multi line text field | No | None | | Submitted at | `submitted_at` | Date time | Yes | None | | Edited at | `edited_at` | Date time | No | None | | Published at | `published_at` | Date time | No | None | | Source | `source` | Single line text field | Yes | None | | Author | `author` | Customer reference | No | When this field is populated, the customer must be present in the merchant's store. | | Author display name | `author_display_name` | Single line text field | No | None | | Order | `order` | Order reference | No | When this field is populated, the order must be present in the merchant's store. | | Product | `product` | Product reference | Yes* | When this field is populated, the product must be present in the merchant's store. | | Product variant | `product_variant` | Product variant reference | No | When this field is populated, the product variant must be present in the merchant's store. | | Merchant reply | `merchant_reply` | Multi line text field | No | None | | Merchant replied at | `merchant_replied_at` | Date time | No | None | | Language | `language` | Language field | No | This should be a valid ISO 639-1 standard language code | | App verification status | `app_verification_status` | Single line text field | Yes* | Validates that the value is one of: `verified_buyer`, `verified_reviewer`, `unverified` | | Media URLs | `media_urls` | URL list | No | None | > Note: > Fields marked with * are required by implementation but not enforced by the schema. This can happen because of the deprecation and renaming of existing fields, or the introduction of new fields. ## Key concepts for building your apps integration to the standard product review metaobject ### Access scopes that must be granted by the merchant In order for your app to syndicate reviews through metaobjects, you'll need to make sure that it has the necessary access scopes. | Scope | Required | Access | |-------|----------|--------| | `write_product_reviews` | Yes | Read, create, or update product review metaobjects and enable the standard metaobject definitions for product reviews | | `read_metaobjects` | Yes | Read any metaobject and subscribe to metaobject webhooks | | `read_customers` | Yes | Read customer information | | `read_orders` | Yes | Read order information | | `read_products` | Yes | Read product information | > Note: > For existing apps that are new to syndication, required permissions can be backfilled after app review by the app review team. The [Shopify OAuth docs](/docs/apps/build/authentication-authorization/access-tokens/authorization-code-grant) contain more information around how to enable access scopes. You'll need to verify that the correct scopes have been added. ### Enabling the metaobject definition To leverage the product review standard definition on a merchant's store, you'll first need to run a mutation to enable the `product_review` standard metaobject definition. This is done by calling the [`standardMetaobjectDefinitionEnable` mutation](/docs/api/admin-graphql/latest/mutations/standardMetaobjectDefinitionEnable): ```graphql mutation ReviewStandardMetaobjectDefinitionEnable { standardMetaobjectDefinitionEnable(type: "product_review") { metaobjectDefinition { id } userErrors { field code message } } } ``` ```json { "data": { "standardMetaobjectDefinitionEnable": { "metaobjectDefinition": { "id": "gid://shopify/MetaobjectDefinition/1800798496" }, "userErrors": [] } }, "extensions": { "cost": { "requestedQueryCost": 10, "actualQueryCost": 10, "throttleStatus": { "maximumAvailable": 2000, "currentlyAvailable": 1990, "restoreRate": 100 } } } } ``` ## Webhook subscription Before you can subscribe to the product review metaobject [webhooks](/docs/api/webhooks), you'll need to ensure you have the required access scopes and have the product review metaobject definition enabled. To subscribe to product review metaobject webhooks, use the following GraphQL mutation: ```graphql mutation subscribeToWebhook { webhookSubscriptionCreate( topic: METAOBJECTS_CREATE, webhookSubscription: { callbackUrl: "https://example.com", filter: "type:product_review" } ) { webhookSubscription { id format createdAt endpoint { __typename ... on WebhookHttpEndpoint { callbackUrl } } } userErrors { field message } } } ``` ```json { "data": { "webhookSubscriptionCreate": { "webhookSubscription": { "id": "gid://shopify/WebhookSubscription/1716799734048", "format": "JSON", "createdAt": "2025-05-01T14:56:37Z", "endpoint": { "__typename": "WebhookHttpEndpoint", "callbackUrl": "https://example.com/" } }, "userErrors": [] } }, "extensions": { "cost": { "requestedQueryCost": 10, "actualQueryCost": 10, "throttleStatus": { "maximumAvailable": 2000, "currentlyAvailable": 1990, "restoreRate": 100 } } } } ``` Shopify expects your app to subscribe to all metaobject topics when handling webhooks for product review metaobjects. The available topics are: - `METAOBJECTS_CREATE` - `METAOBJECTS_UPDATE` - `METAOBJECTS_DELETE` When you subscribe to metaobject topics, you must provide a `filter` field in the payload to specify the type of metaobject you want to receive webhooks for. To unsubscribe your app from receiving webhooks, use this GraphQL mutation: ```graphql mutation webhookSubscriptionDelete { webhookSubscriptionDelete(id: "gid://shopify/WebhookSubscription/525699895") { userErrors { field message } deletedWebhookSubscriptionId } } ``` ```json { "data": { "webhookSubscriptionDelete": { "userErrors": [], "deletedWebhookSubscriptionId": "gid://shopify/WebhookSubscription/1716799734048" } }, "extensions": { "cost": { "requestedQueryCost": 10, "actualQueryCost": 10, "throttleStatus": { "maximumAvailable": 2000, "currentlyAvailable": 1990, "restoreRate": 100 } } } } ``` ### Webhook payload For `METAOBJECTS_CREATE` and `METAOBJECTS_UPDATE` lifecycle events, your app receives the payload for the product review metaobject, as shown below. Note that empty fields may not be populated. ```json { "type": "product_review", "handle": "product-review-1", "created_at": "2022-11-28T14:23:11+00:00", "updated_at": "2023-09-20T23:02:09+01:00", "id": "gid://shopify/Metaobject/1", "definition_id": "gid://shopify/MetaobjectDefinition/2", "fields": { "app_verification_status": "verified_buyer", "author": "gid://shopify/Customer/1", "author_display_name": "John", "body": "Great shirt!", "edited_at": "2023-06-30T14:39:13Z", "language": "en", "media_urls": "[\"http://example.com/image.jpg\"]", "merchant_replied_at": "2023-06-30T14:39:13Z", "merchant_reply": "Thank you for buying our product!", "order": "gid://shopify/Order/3", "product": "gid://shopify/Product/4", "product_variant": "gid://shopify/ProductVariant/5", "published_at": "2022-11-28T14:22:52Z", "rating": "{\"scale_min\":\"1.0\",\"scale_max\":\"5.0\",\"value\":\"5.0\"}", "reviewed_order": "gid://shopify/Order/3", "reviewed_product": "gid://shopify/Product/4", "reviewed_variant": "gid://shopify/ProductVariant/5", "source": "email", "submitted_at": "2022-11-28T14:22:52Z", "title": "Love it!" }, "created_by_staff_id": null, "created_by_app_id": "gid://shopify/ApiClient/1", "capabilities": { "publishable": { "status": "active" } } } ``` For `METAOBJECTS_DELETE`, your app receives: ```json { "id": "gid://shopify/Metaobject/1", "type": "product_review", "handle": "prouct-review-1", "created_by_app_id": "gid://shopify/ApiClient/1" } ``` ## Managing reviews with the GraphQL Admin API ### Getting a review metaobject You can read metaobjects by `id` using the [`metaobject` query](/docs/api/admin-graphql/latest/queries/metaobject). Alternatively, you can read metaobjects by their handle using the [`metaobjectByHandle` query](/docs/api/admin-graphql/latest/queries/metaobjectbyhandle). Example GraphQL: ```graphql query Metaobject { metaobject(id: "gid://shopify/Metaobject/35225622") { id createdBy { id } rating: field(key: "rating") { value } title: field(key: "title") { value } body: field(key: "body") { value } product: field(key: "product") { value } productVariant: field(key: "product_variant") { value } order: field(key: "order") { value } author: field(key: "author") { value } authorDisplayName: field(key: "author_display_name") { value } submittedAt: field(key: "submitted_at") { value } editedAt: field(key: "edited_at") { value } merchantReply: field(key: "merchant_reply") { value } merchantRepliedAt: field(key: "merchant_replied_at") { value } mediaUrls: field(key: "media_urls") { value } language: field(key: "language") { value } appVerificationStatus: field(key: "app_verification_status") { value } handle type capabilities { publishable { status } } updatedAt } } ``` ```json { "data": { "metaobject": { "id": "gid://shopify/Metaobject/35225622", "createdBy": { "id": "gid://shopify/App/12345" }, "rating": { "value": "{\"scale_min\":\"1.0\",\"scale_max\":\"5.0\",\"value\":\"5.0\"}" }, "title": { "value": "Awesome" }, "body": { "value": "Great product!" }, "product": { "value": "gid://shopify/Product/1234567" }, "productVariant": { "value": null }, "order": { "value": "gid://shopify/Order/1234567" }, "author": { "value": "gid://shopify/Customer/1234567" }, "authorDisplayName": { "value": "John" }, "submittedAt": { "value": "2022-11-30T15:18:27Z" }, "editedAt": { "value": "2023-06-30T15:22:09Z" }, "merchantReply": { "value": "Thank you for purchasing our product!" }, "merchantRepliedAt": { "value": "2023-06-30T15:22:09Z" }, "mediaUrls": { "value": "[\"https://example.com/images/image1.jpg\"]" }, "language": { "value": "en-US" }, "appVerificationStatus": { "value": "verified_buyer" }, "handle": "review-ef1d9f1e-d0f6-40cd-97c8-1234567", "type": "product_review", "capabilities": { "publishable": { "status": "ACTIVE" } }, "updatedAt": "2023-08-04T09:38:59Z" } }, "extensions": { "cost": { "requestedQueryCost": 14, "actualQueryCost": 14 } } } ``` ### Getting multiple review metaobjects in bulk Use a [bulk query](/docs/api/usage/bulk-operations/queries) to get all product review metaobjects for a shop. You can select as many fields as your app requires, as shown in the previous example. ```graphql mutation BulkQuery { bulkOperationRunQuery(query: "{ metaobjects(type: \"product_review\") { edges { node { id createdBy { id } rating: field(key: \"rating\") { value } title: field(key: \"title\") { value } body: field(key: \"body\") { value } reviewedProduct: field(key: \"product\") { value } productVariant: field(key: \"product_variant\") { value } order: field(key: \"order\") { value } author: field(key: \"author\") { value } authorDisplayName: field(key: \"author_display_name\") { value } submittedAt: field(key: \"submitted_at\") { value } editedAt: field(key: \"edited_at\") { value } handle type capabilities { publishable { status } } updatedAt } } }}") { bulkOperation { id url } userErrors { field message } } } ``` ```json { "data": { "bulkOperationRunQuery": { "bulkOperation": { "id": "gid://shopify/BulkOperation/1234567", "url": null }, "userErrors": [] } }, "extensions": { "cost": { "requestedQueryCost": 10, "actualQueryCost": 10, "throttleStatus": { "maximumAvailable": 2000, "currentlyAvailable": 1990, "restoreRate": 100 } } } } ``` The following example fetches data asynchronously and stores it in a `JSONL` file. When it finishes, you can fetch the file with the [`currentBulkOperation` query](/docs/api/admin-graphql/latest/queries/currentbulkoperation). You can only run one bulk operation (query or mutation) at a time, and you can only fetch the most recent operation. Previous bulk operation results are not available. ```graphql query CheckBulkOperation { currentBulkOperation { id partialDataUrl fileSize objectCount url status } } ``` ```json { "data": { "currentBulkOperation": { "id": "gid://shopify/BulkOperation/5885752410400", "partialDataUrl": null, "fileSize": "1423", "objectCount": "2", "url": "https://example.com", "status": "COMPLETED" } }, "extensions": { "cost": { "requestedQueryCost": 1, "actualQueryCost": 1, "throttleStatus": { "maximumAvailable": 2000, "currentlyAvailable": 1999, "restoreRate": 100 } } } } ``` The response from the `currentBulkOperation` contains a URL link to a file with the response. To ensure the creation of the corresponding metaobject, it's important to inspect the file for any `userErrors` that occurred during the operation. If any `userErrors` are found, the metaobject is not created. ### Creating a review metaobject You can create reviews by issuing a [`metaobjectUpsert` mutation](/docs/api/admin-graphql/latest/mutations/metaobjectupsert). ```graphql mutation MetaobjectUpsert { metaobjectUpsert( handle: { handle: "review-1", type: "product_review" }, metaobject: { fields: [ { key: "rating", value: "{\"scale_min\":\"1\", \"scale_max\": \"5\", \"value\": \"4.0\"}" } ] } ) { metaobject { type handle rating: field(key: "rating") { value } } userErrors { code field message } } } ``` ```json { "data": { "metaobjectUpsert": { "metaobject": { "type": "product_review", "handle": "review-1", "rating": { "value": "{\"scale_min\":\"1.0\",\"scale_max\":\"5.0\",\"value\":\"4.0\"}" } }, "userErrors": [] } }, "extensions": { "cost": { "requestedQueryCost": 11, "actualQueryCost": 11, "throttleStatus": { "maximumAvailable": 2000, "currentlyAvailable": 1989, "restoreRate": 100 } } } } ``` An example `MetaobjectUpsertInput`. Note that this example is incomplete, with only one field. You must supply all available and required fields. ```json { "handle": { "handle": "review-1", "type": "product_review" }, "input": { "fields": [ { "key": "rating", "value": "{\"scale_min\":\"1\", \"scale_max\": \"5\", \"value\": \"4.0\"}" } ] } } ``` ### Updating a review metaobject To update a review, use the same `metaobjectUpsert` mutation used for creating a new review. This mutation updates an existing review or creates a new review if it doesn't already exist. When you provide a list of fields to update, those fields are overwritten and the final metaobject is merged with the remaining fields left intact. ### Bulk upserting multiple review metaobjects Upload a `JSONL` file containing newline-separated JSON objects with the mutation parameters with the `stagedUploadCreate` mutation. You can provide as many fields you wish to update. Refer to all available and required fields. There is a hard limit of 20MB on the `JSONL` file. You can partition the `JSONL` file into batches of 10,000 reviews to ensure it does not exceed this limit. ```json { "handle": { "handle": "1234-fe28285a-4b39-4993-8f57-db3cfbf53a95", "type": "product_review" }, "input": { "type": "product_review", "handle": "1234-fe28285a-4b39-4993-8f57-db3cfbf53a95", "fields": [ { "key": "title", "value": "this is an awesome product" }, { "key": "rating", "value": "{\"scale_min\":\"1\", \"scale_max\": \"5\", \"value\": \"5.0\"}" }, { "key": "body", "value": "will buy this again" } ] } } { "handle": { "handle": "1234-3f5b7d20-4a6b-4b7e-8a8e-3b2cd8d1f3e1", "type": "product_review" }, "input": { "type": "product_review", "handle": "1234-3f5b7d20-4a6b-4b7e-8a8e-3b2cd8d1f3e1", "fields": [ { "key": "title", "value": "loved it" }, { "key": "rating", "value": "{\"scale_min\":\"1\", \"scale_max\": \"5\", \"value\": \"4.0\"}" }, { "key": "body", "value": "Exactly what I needed" } ] } } ``` Pass the resulting path to the staged upload to a `bulkOperationRunMutation` that performs the mutations asynchronously. ```graphql mutation { bulkOperationRunMutation( mutation: "mutation MetaobjectUpsert($input: MetaobjectUpsertInput!) { metaobjectUpsert(metaobject: $input) { metaobject { type handle } } userErrors { message field } }", stagedUploadPath: "tmp/21759409/bulk/89e620e1-0252-43b0-8f3b-3b7075ba4a23/bulk_op_vars") { bulkOperation { id url status } userErrors { message field } } } ``` ```json { "data": { "bulkOperationRunMutation": { "bulkOperation": { "id": "gid://shopify/BulkOperation/123456", "url": null, "status": "CREATED" }, "userErrors": [] } }, "extensions": { "cost": { "requestedQueryCost": 10, "actualQueryCost": 10, "throttleStatus": { "maximumAvailable": 2000, "currentlyAvailable": 1990, "restoreRate": 100 } } } } ``` The [guidelines for bulk importing data](/docs/api/usage/bulk-operations) provide more detailed information around bulk operations. ### Deleting a single review metaobject Deleting a single review metaobject can be done through the [`metaobjectDelete` mutation](/docs/api/admin-graphql/latest/mutations/metaobjectdelete). ### Bulk deleting multiple review metaobjects Multiple reviews can be deleted through the [`metaobjectBulkDelete` mutation](/docs/api/admin-graphql/latest/mutations/metaobjectbulkdelete). There's a limit of 250 review IDs per request. ### Mark a review metaobject as published or unpublished In order to mark a review as published or unpublished in its metaobject, supply two things in the `metaobjectUpdate` payload: - Publishable status capability: - `{"capabilities": {"publishable": {"status": "ACTIVE"}}` when published - `{"capabilities": {"publishable": {"status": "DRAFT"}}` when unpublished - `published_at`: - A UTC timestamp when published - `null` when unpublished ```graphql mutation PublishMetaobject { metaobjectUpdate(id: "gid://shopify/Metaobject/4", metaobject: { capabilities: { publishable: { status: ACTIVE } }, fields: [ { key: "published_at", value: "2022-08-26T12:09:46+00:00" } ] }) { metaobject { id fields { key value } capabilities { publishable { status } } } userErrors { code field message } } } ``` ```json { "data": { "metaobjectUpdate": { "metaobject": { "id": "gid://shopify/Metaobject/123456", "fields": [ { "key": "rating", "value": "{\"scale_min\":\"1.0\",\"scale_max\":\"5.0\",\"value\":\"5.0\"}" }, { "key": "title", "value": "This is an awesome product!" }, { "key": "body", "value": "Will buy this again!" }, { "key": "submitted_at", "value": "2023-08-04T09:38:59Z" }, { "key": "edited_at", "value": null }, { "key": "published_at", "value": "2022-08-26T12:09:46+00:00" }, { "key": "source", "value": "email" }, { "key": "author", "value": null }, { "key": "author_display_name", "value": "John" }, { "key": "order", "value": null }, { "key": "product", "value": "gid://shopify/Product/123456" }, { "key": "product_variant", "value": null }, { "key": "merchant_reply", "value": null }, { "key": "merchant_replied_at", "value": null }, { "key": "media_urls", "value": null }, { "key": "language", "value": null }, { "key": "app_verification_status", "value": null } ], "capabilities": { "publishable": { "status": "ACTIVE" } } }, "userErrors": [] } }, "extensions": { "cost": { "requestedQueryCost": 13, "actualQueryCost": 13, "throttleStatus": { "maximumAvailable": 2000, "currentlyAvailable": 1987, "restoreRate": 100 } } } } ``` ```graphql mutation UnpublishMetaobject { metaobjectUpdate(id: "gid://shopify/Metaobject/4", metaobject: { capabilities: { publishable: { status: DRAFT } }, fields: [ { key: "published_at", value: "" } ] }) { metaobject { id fields { key value } capabilities { publishable { status } } } userErrors { code field message } } } ``` ```json { "data": { "metaobjectUpdate": { "metaobject": { "id": "gid://shopify/Metaobject/123456", "fields": [ { "key": "rating", "value": "{\"scale_min\":\"1.0\",\"scale_max\":\"5.0\",\"value\":\"5.0\"}" }, { "key": "title", "value": "This is an awesome product!" }, { "key": "body", "value": "Will buy this again!" }, { "key": "submitted_at", "value": "2023-08-04T09:38:59Z" }, { "key": "edited_at", "value": null }, { "key": "published_at", "value": null }, { "key": "source", "value": "email" }, { "key": "author", "value": null }, { "key": "author_display_name", "value": "John" }, { "key": "order", "value": null }, { "key": "product", "value": "gid://shopify/Product/123456" }, { "key": "product_variant", "value": null }, { "key": "merchant_reply", "value": null }, { "key": "merchant_replied_at", "value": null }, { "key": "media_urls", "value": null }, { "key": "language", "value": null }, { "key": "app_verification_status", "value": null } ], "capabilities": { "publishable": { "status": "DRAFT" } } }, "userErrors": [] } }, "extensions": { "cost": { "requestedQueryCost": 13, "actualQueryCost": 13, "throttleStatus": { "maximumAvailable": 2000, "currentlyAvailable": 1987, "restoreRate": 100 } } } } ``` ### Importing and exporting reviews with handles If you support importing reviews from another partner via CSV import, you should avoid duplicating reviews in metaobjects. When a [metaobject handle](/docs/api/storefront/latest/objects/metaobject#field-handle) is present for an imported review, source the review from the [metaobjects API](/docs/api/admin-graphql/latest/queries/metaobject). To support review importing, do the following things: - Update your review export flow to include the review's metaobject handle. - Update your review import flows to support reading [metaobject handles](/docs/api/storefront/latest/objects/metaobject#field-handle). - Shop reviews from the CSV import should be sourced from the metaobjects API. - Partner reviews from the CSV import with handles should be sourced from the metaobjects API. - To support all merchant imported fields, review data sourced from the metaobjects API should be enriched with the data from CSV imports. For example, custom questions. - To prevent outdated reviews in your app, handle all metaobject webhook events for merchants with your app installed regardless of which app ID has created the metaobject. The partner app responsible for creating the metaobject can be identified by reading the `createdByAppId` field. It is important to note that this field is immutable and is always attributed to the app that created the metaobject. This means that not all metaobjects for your active merchants will show the `createdByAppId` field, populated with your app ID. Furthermore, if your application supports exporting and importing reviews from one Shopify store to another, you should recreate the metaobjects using the same handle. Shop may not be able to ingest such reviews, since the imported reviews will not have correct order, product or customer references for the new store. ### Testing Before you can test the integration, you'll need a test store that is configured to be visible in the Shop App. Your test store must meet the following conditions: - The Shop channel is installed. - The app for which you are testing the integration is installed. - The store has at least one test product that can be purchased. - Test payments have been enabled, or there's a 100% order discount you can use to complete the checkout. To test your development shop with the Shop app integration: 1. Install the Shop app on your development store from the Shopify App Store 2. From the Shop app in your store admin, toggle on development mode to enable testing functionality When your test store meets these conditions, let the Shop team know, so they can enable your store. The steps outlined in the test plan may vary depending on the features available in your app. ### Handling validation errors GraphQL mutations return validation errors in the `userErrors` field. These errors can be a good first indication of how well your app's syndication of reviews to metaobjects is working. When syndicating reviews to metaobjects, common integration errors usually have to do with incorrectly provided values, especially around reference fields. You should review the validation errors you receive and make the required adjustments to your integration, to ensure as many reviews can be syndicated to metaobjects as possible. That being said, reference fields are more complicated to handle and fix, as your app may have a resource ID that's not present on the merchant's store at the time of review syndication. In such a scenario, retry the review syndication without the resource ID. #### Encountering internal server errors To account for intermittent internal server errors from Shopify's APIs, your app should use retry logic to ensure recovery and eventual success. For major outages, refer to [Shopify's status page](https://www.shopifystatus.com/). #### Encountering rate limits [Shopify API rate limits](/docs/api/usage/limits#rate-limits) are applicable to all requests for metaobjects, and handling large volumes of incoming webhooks may throttle the API requests. Consider the following strategies for handling webhook requests in your app: - Handling incoming webhook requests asynchronously - Having a retry/backoff strategy when making requests to Shopify APIs - Implementing rate limiting internally within your app to reduce the chance of your app getting throttled by Shopify