--- title: Contextual product feeds description: >- Learn how to use product feeds to synchronize contextualized product listings to your platform. source_url: html: 'https://shopify.dev/docs/apps/build/sales-channels/contextual-product-feeds' md: >- https://shopify.dev/docs/apps/build/sales-channels/contextual-product-feeds.md --- # Contextual product feeds Contextual product feeds allow [sales channels](https://shopify.dev/docs/apps/build/sales-channels) to sync merchant catalogs to their platforms. You can configure distinct feeds for the different language and country pairs that a merchant supports. *** ## What you'll learn In this tutorial, you'll learn how to do the following tasks: * Identify a merchant's supported markets * Create product feeds for specific country/language contexts * Subscribe to product feed webhooks * Initiate a full product sync *** ## Requirements * Your app has the `read_product_listings` [access scope](https://shopify.dev/docs/api/usage/access-scopes). Learn more about requesting access scopes when your app is installed using [authorization code grant](https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens/authorization-code-grant). * To use the [Storefront API](#storefront-api-recommended) for retrieving localization data, your app needs [Storefront API access](https://shopify.dev/docs/api/storefront#authentication). *** ## Step 1: Identify the merchant's supported markets Query the merchant's available countries and their supported languages using one of the methods below. ### Unified Markets With [Unified Markets](https://help.shopify.com/en/manual/markets-new/overview), a region can appear in multiple markets. Shopify also assigns a backup region to buyers who don't have a corresponding market. You can check if Unified Markets is enabled on a store: ## Check if Unified Markets is enabled ```graphql query shopFeatures { shop { features { unifiedMarkets } } } ``` ### Storefront API (recommended) Use the Storefront API's [`localization` query](https://shopify.dev/docs/api/storefront/latest/queries/localization) to retrieve a merchant's available countries and their supported languages. This method works for all stores regardless of whether Unified Markets is enabled. ## POST https://{shop}.myshopify.com/api/{api\_version}/graphql.json ## Localization query ```graphql query CountriesAndLanguages { localization { availableCountries { name isoCode currency { symbol isoCode } defaultLanguage { name isoCode } availableLanguages { name isoCode } } } } ``` ## JSON response ```json { "data": { "localization": { "availableCountries": [ { "name": "Canada", "isoCode": "CA", "currency": { "symbol": "$", "isoCode": "CAD" }, "defaultLanguage": { "name": "English", "isoCode": "EN" }, "availableLanguages": [ { "name": "English", "isoCode": "EN" }, { "name": "French", "isoCode": "FR" } ] }, { "name": "United States", "isoCode": "US", "currency": { "symbol": "$", "isoCode": "USD" }, "defaultLanguage": { "name": "English", "isoCode": "EN" }, "availableLanguages": [ { "name": "English", "isoCode": "EN" } ] } ] } } } ``` **Note:** The [`defaultLanguage`](https://shopify.dev/docs/api/storefront/latest/objects/Country#field-defaultLanguage) field only returns data for stores with Unified Markets enabled. For stores without Unified Markets, use the first language in `availableLanguages` as the default. ### Graph​QL Admin API You can also use the GraphQL Admin API's [`markets` query](https://shopify.dev/docs/api/admin-graphql/latest/queries/markets) to obtain localization details. **Note:** Definitions for "primary region" might differ for each channel. Shopify exposes the following fields to help channels identify which regions and markets the merchant is based in: * The merchant's [primary market](https://shopify.dev/docs/api/admin-graphql/latest/objects/Market#field-market-primary). * The `country` of the merchant's [primary fulfillment location](https://shopify.dev/docs/api/admin-graphql/latest/queries/location)'s [`address`](https://shopify.dev/docs/api/admin-graphql/2024-07/objects/Location#field-address). * The merchant's [default currency](https://shopify.dev/docs/api/admin-graphql/latest/objects/Shop#field-shop-currencycode). **Note:** For stores with Unified Markets, the `webPresence` field returns `null` if the market doesn't have its own web presence configured. The following example shows how to retrieve language, currency, and country details from a shop's markets. [Inline fragments](https://shopify.dev/docs/apps/build/graphql/basics/advanced#inline-fragments) are used to simplify the GraphQL query. ## POST https://{shop}.myshopify.com/api/{api\_version}/graphql.json ## Markets query ```graphql { markets(first: 10, enabled: true) { edges { node { name primary enabled currencySettings { baseCurrency { currencyCode enabled } localCurrencies } webPresence { subfolderSuffix domain { host } defaultLocale { ...LocaleFields } alternateLocales { ...LocaleFields } } regions(first: 20) { edges { node { id name ... on MarketRegionCountry { code } } } } } } } } fragment LocaleFields on ShopLocale { locale name primary published } ``` ## JSON response ```json { "data": { "markets": { "edges": [ { "node": { "name": "Canada", "primary": true, "enabled": true, "currencySettings": { "baseCurrency": { "currencyCode": "CAD", "enabled": false }, "localCurrencies": true }, "webPresence": { "subfolderSuffix": null, "domain": { "host": "my-store.myshopify.com" }, "defaultLocale": { "locale": "en", "name": "English", "primary": true, "published": true }, "alternateLocales": [ { "locale": "fr", "name": "French", "primary": false, "published": true } ] }, "regions": { "edges": [ { "node": { "id": "gid://shopify/MarketRegionCountry/144848879672", "name": "Canada", "code": "CA" } } ] } } }, { "node": { "name": "USA", "primary": false, "enabled": true, "currencySettings": { "baseCurrency": { "currencyCode": "USD", "enabled": false }, "localCurrencies": false }, "webPresence": { "subfolderSuffix": "us", "domain": null, "defaultLocale": { "locale": "en", "name": "English", "primary": true, "published": true }, "alternateLocales": [] }, "regions": { "edges": [ { "node": { "id": "gid://shopify/MarketRegionCountry/145364320312", "name": "United States", "code": "US" } } ] } } } ] } } } ``` ### Subscribe to market updates Market configurations might change after [onboarding](https://shopify.dev/docs/apps/build/sales-channels#sales-channel-app-flow), to which your app may need to handle depending on how you treat primary markets. Stay updated on any market changes by using the [`webhookSubscriptionCreate` mutation](https://shopify.dev/docs/api/admin-graphql/latest/mutations/webhookSubscriptionCreate) to subscribe to the [`MARKETS_CREATE`](https://shopify.dev/docs/api/admin-graphql/latest/enums/WebhookSubscriptionTopic#value-marketscreate), [`MARKETS_DELETE`](https://shopify.dev/docs/api/admin-graphql/latest/enums/WebhookSubscriptionTopic#value-marketsdelete), and [`MARKETS_UPDATE`](https://shopify.dev/docs/api/admin-graphql/latest/enums/WebhookSubscriptionTopic#value-marketsupdate) webhook topics. Events are triggered whenever key changes occur, such as when: * A new market is created. * An existing market is updated. * An existing market is deleted. ### Subscribe to backup region updates For stores with Unified Markets enabled, the backup region determines the default experience for buyers who Shopify can't match to a configured market. Subscribe to the [`MARKETS_BACKUP_REGION_UPDATE`](https://shopify.dev/docs/api/admin-graphql/latest/enums/WebhookSubscriptionTopic#value-marketsbackupregionupdate) webhook topic to receive notifications when the merchant's backup region changes. ## markets\_backup\_region/update ```json { "admin_graphql_api_id": "gid://shopify/MarketRegion/202354458680", "code": "FR" } ``` *** ## Step 2: Create product feeds Use the [`productFeedCreate` mutation](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productFeedCreate) to create a `ProductFeed` for each country/language pair that both the channel and the merchant support. Save the `productFeed`'s `id` value. You'll use it it initiate and identify webhook payloads in a [subsequent step](#step-4-initiate-a-full-sync) when you synchronize products. The following example shows how to create a product feed for the `US-EN` context. ## POST https://{shop}.myshopify.com/api/{api\_version}/graphql.json ## Create product feed ```graphql mutation { productFeedCreate(input: {country: US, language: EN}) { productFeed { id country language } userErrors { field message } } } ``` ## JSON response ```json { "data": { "productFeedCreate": { "productFeed": { "id": "gid://shopify/ProductFeed/1343510", "country": "US", "language": "EN" }, "userErrors": [] } } } ``` ### (Optional): Use the primary context only If your app only requires access to the merchant's default localization, then use the [`productFeedCreate` mutation](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productFeedCreate#argument-input) without its `input` argument. This creates a feed in the merchant's primary context, similar to how the [`ProductListings`](https://shopify.dev/docs/api/admin-rest/latest/resources/productlisting) reference on the REST Admin API behaves. **Unified Markets behavior:** For stores with Unified Markets enabled, calling `productFeedCreate` without an `input` argument creates a product feed using the **backup region** as the country. To determine the default language for a given country, use the Storefront API's [`localization` query](#storefront-api-recommended) and check the `defaultLanguage` field. You can also query the [`backupRegion`](https://shopify.dev/docs/api/admin-graphql/latest/queries/backupRegion) to get the current backup region. *** ## Step 3: Subscribe to product feed webhooks Subscribe to product feed webhooks to receive initial [full sync payloads](#step-4-initiate-a-full-sync) and [async events](#subscribe-to-incremental-sync-events) whenever products are updated. ### Subscribe to full sync events Full sync events are triggered when a product feed full sync is initiated. Full sync events contain individual product payloads and allow you to sync a merchant's entire catalog during channel setup. Use the [`webhookSubscriptionCreate` mutation](https://shopify.dev/docs/api/admin-graphql/latest/mutations/webhookSubscriptionCreate) to subscribe to the [`PRODUCT_FEEDS_FULL_SYNC`](https://shopify.dev/docs/api/admin-graphql/latest/enums/WebhookSubscriptionTopic#value-productfeedsfullsync) webhook topic. ## Subscribe to full sync topic ```graphql mutation { webhookSubscriptionCreate( topic: PRODUCT_FEEDS_FULL_SYNC webhookSubscription: { uri: "{your-callback-uri}" format: JSON } ) { webhookSubscription { id topic uri createdAt } userErrors { field message } } } ``` Individual product listing payloads are sent when a [full sync is initiated](#step-4-initiate-a-full-sync). ## product\_feeds/full\_sync ```json { "metadata": { "action": "CREATE", "type": "FULL", "resource": "PRODUCT", "fullSyncId": "gid://shopify/ProductFullSync/1123511235", "truncatedFields": [ ], "occurred_at": "2024-01-01T00:00:00.000Z" }, "productFeed": { "id": "gid://shopify/ProductFeed/12345", "shop_id": "gid://shopify/Shop/12345", "country": "CA", "language": "EN" }, "product": { "id": "gid://shopify/Product/12345", "title": "Coffee", "description": "The best coffee in the world", "onlineStoreUrl": "https://example.com/products/coffee", "createdAt": "2024-12-31T19:00:00-05:00", "updatedAt": "2024-12-31T19:00:00-05:00", "isPublished": true, "publishedAt": "2024-12-31T19:00:00-05:00", "productType": "Coffee", "vendor": "Cawfee Inc", "handle": "", "images": { "edges": [ { "node": { "id": "gid://shopify/ProductImage/394", "url": "https://cdn.shopify.com/s/files/1/0262/9117/5446/products/IMG_0022.jpg?v=1675101331", "height": 3024, "width": 4032 } } ] }, "options": [ { "name": "Title", "values": [ "151cm", "155cm", "158cm" ] } ], "seo": { "title": "seo title", "description": "seo description" }, "tags": [ "tag1", "tag2" ], "variants": { "edges": [ { "node": { "id": "gid://shopify/ProductVariant/1", "title": "151cm", "price": { "amount": "100.00", "currencyCode": "CAD" }, "compareAtPrice": null, "sku": "12345", "barcode": null, "quantityAvailable": 10, "availableForSale": true, "weight": 2.3, "weightUnit": "KILOGRAMS", "requireShipping": true, "inventoryPolicy": "DENY", "createdAt": "2024-12-31T19:00:00-05:00", "updatedAt": "2024-12-31T19:00:00-05:00", "image": { "id": "gid://shopify/ProductImage/394", "url": "https://cdn.shopify.com/s/files/1/0262/9117/5446/products/IMG_0022.jpg?v=1675101331", "height": 3024, "width": 4032 }, "selectedOptions": [ { "name": "Title", "value": "151cm" } ] } } ] } }, "products": null } ``` ### (Optional): Subscribe to full sync completion events You can subscribe to the [`PRODUCT_FEEDS_FULL_SYNC_FINISH`](https://shopify.dev/docs/api/admin-graphql/latest/enums/WebhookSubscriptionTopic#value-productfeedsfullsyncfinish) webhook topic to be notified whenever a full sync finishes. This is a useful signal for scenarios where the merchant hasn't published any products to the channel. ```json { "metadata": { "action": "CREATE", "type": "FULL", "resource": "PRODUCT", "fullSyncId": "gid://shopify/ProductFullSync/1123511235", "truncatedFields": [ ], "occurred_at": "2024-01-01T00:00:00.000Z" }, "productFeed": { "id": "gid://shopify/ProductFeed/12345", "shop_id": "gid://shopify/Shop/12345", "country": "CA", "language": "EN" }, "fullSync": { "createdAt": "2024-12-31 19:00:00 -0500", "errorCode": null, "status": "completed", "count": 12, "url": null } } ``` ### Subscribe to incremental sync events Incremental sync events are triggered whenever changes occur relative to app's feeds, such as when: * Product fields are updated. * Product variant fields are added, updated, or deleted. * Product translations are updated. * Product market price is updated. * Products are published to the app. * Products are unpublished from app. Use the [`webhookSubscriptionCreate` mutation](https://shopify.dev/docs/api/admin-graphql/latest/mutations/webhookSubscriptionCreate) to subscribe to the [`PRODUCT_FEEDS_INCREMENTAL_SYNC`](https://shopify.dev/docs/api/admin-graphql/latest/enums/WebhookSubscriptionTopic#value-productfeedsincrementalsync) webhook topic. ## Subscribe to incremental sync topic ```graphql mutation { webhookSubscriptionCreate( topic: PRODUCT_FEEDS_INCREMENTAL_SYNC webhookSubscription: { uri format: JSON } ) { webhookSubscription { id topic uri createdAt } userErrors { field message } } } ``` ## product\_feeds/incremental\_sync ```json { "metadata": { "action": "CREATE", "type": "INCREMENTAL", "resource": "PRODUCT", "truncatedFields": [ ], "occured_at": "2024-12-31T19:00:00-05:00" }, "productFeed": { "id": "gid://shopify/ProductFeed/12345", "shop_id": "gid://shopify/Shop/12345", "country": "CA", "language": "EN" }, "product": { "id": "gid://shopify/Product/12345", "title": "Coffee", "description": "The best coffee in the world", "onlineStoreUrl": "https://example.com/products/coffee", "createdAt": "2024-12-31T19:00:00-05:00", "updatedAt": "2024-12-31T19:00:00-05:00", "isPublished": true, "publishedAt": "2024-12-31T19:00:00-05:00", "productType": "Coffee", "vendor": "Cawfee Inc", "handle": "", "images": { "edges": [ { "node": { "id": "gid://shopify/ProductImage/394", "url": "https://cdn.shopify.com/s/files/1/0262/9117/5446/products/IMG_0022.jpg?v=1675101331", "height": 3024, "width": 4032 } } ] }, "options": [ { "name": "Title", "values": [ "151cm", "155cm", "158cm" ] } ], "seo": { "title": "seo title", "description": "seo description" }, "tags": [ "tag1", "tag2" ], "variants": { "edges": [ { "node": { "id": "gid://shopify/ProductVariant/1", "title": "151cm", "price": { "amount": "100.00", "currencyCode": "CAD" }, "compareAtPrice": null, "sku": "12345", "barcode": null, "quantityAvailable": 10, "availableForSale": true, "weight": 2.3, "weightUnit": "KILOGRAMS", "requireShipping": true, "inventoryPolicy": "DENY", "createdAt": "2024-12-31T19:00:00-05:00", "updatedAt": "2024-12-31T19:00:00-05:00", "image": { "id": "gid://shopify/ProductImage/394", "url": "https://cdn.shopify.com/s/files/1/0262/9117/5446/products/IMG_0022.jpg?v=1675101331", "height": 3024, "width": 4032 }, "selectedOptions": [ { "name": "Title", "value": "151cm" } ] } } ] } }, "products": null } ``` ### Subscribe to product feed updates Product feeds might change as the merchant modifies their market settings. For example, if a merchant un-publishes a language, then any dependant feeds will become inactive. Subscribe to the [`PRODUCT_FEEDS_UPDATE`](https://shopify.dev/docs/api/admin-graphql/latest/enums/WebhookSubscriptionTopic#value-productfeedsupdate) webhook topic to stay updated on [feed `statuses`](https://shopify.dev/docs/api/admin-graphql/latest/objects/ProductFeed#field-status). ## product\_feeds/update ```json { "id": "gid://shopify/ProductFeed/1285521464", "country": "CA", "language": "FR", "status": "inactive" } ``` *** ## Step 4: Initiate a full sync After all relevant feeds are created and your app has subscribed to the necessary webhook topics, you can initiate full syncs for each feed using the [`productFullSync`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productFullSync). The [`id`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productFullSync#field-productfullsyncpayload-id) returned can be used to attribute the webhooks and identify the sync completion event. Your app should trigger a full sync once during initialization to fetch the store's entire catalog. You can trigger additional full syncs on a regular schedule for [reconciliation](https://shopify.dev/docs/apps/build/webhooks/best-practices#implement-reconciliation-jobs). When a full sync is initiated, individual [`PRODUCT_FEEDS_FULL_SYNC` webhook payloads](#subscribe-to-full-sync-events) are fired for each of the merchant's published products. The webhook payloads contain localized data according the feed's context. A confirmation event is sent to the [`PRODUCT_FEEDS_FULL_SYNC_FINISH` subscription](#optional-subscribe-to-full-sync-completion-events) once the full sync has completed. The payload's `fullSyncId` is the feed's identifier. An initial full sync should be triggered during app onboarding, and regular reconciliation syncs should be scheduled after. The optional `beforeUpdatedAt` and `updatedAtSince` parameters allow you to specify a time range for the sync. ## POST https://{shop}.myshopify.com/api/{api\_version}/graphql.json ## start a product full sync ```graphql mutation { productFullSync(id: "gid://shopify/ProductFeed/1164017720") { id userErrors { field message } } } ``` ## JSON response ```json { "data": { "productFullSync": { "id": "gid://shopify/ProductFullSync/2523324778944713391", "userErrors": [] } } } ``` ### (Optional): Full Sync by download **Developer preview:** Full sync by download is in an invite-only developer preview. Access to this functionality is currently invite-only. To learn more, contact your Shopify solutions representative or [Shopify Partner Support](https://help.shopify.com/en/support/partners) An alternative solution is to download a single [`JSONL` file](https://jsonlines.org/) containing all products that would normally be sent through individual [`PRODUCT_FEEDS_FULL_SYNC`](#subscribe-to-full-sync-events) payloads. This can be useful in scenarios where the amount of webhook traffic is difficult to manage. When full sync by download is enabled, the `PRODUCT_FEEDS_FULL_SYNC_FINISH` webhook payload contains a `JSONL` file URL. This functionality is similar to [bulk operations](https://shopify.dev/docs/api/usage/bulk-operations/queries). ## product\_feeds/full\_sync\_finish ```json { "metadata": { "action": "CREATE", "type": "FULL", "resource": "FULL_SYNC", "fullSyncId": "gid://shopify/ProductFullSync/23104717021717166290741", "truncatedFields": [] }, "productFeed": { "id": "gid://shopify/ProductFeed/2310471702", "shop_id": "gid://shopify/Shop/59810447382", "country": "US", "language": "EN" }, "fullSync": { "createdAt": "2024-05-31T14:38:10Z", "errorCode": "", "status": "completed", "count": 16, "url": "https://storage.googleapis.com/shopify-tiers-assets-prod-us-east1/bulk-operation-outputs/dyfkl3g72empyyoenvmtidlm9o4g?bulk-4258087174166.jsonl&response-content-type=application%2Fjsonl" } } ``` *** ## Next steps * For other channel product sync solutions, refer to the [unidirectional product synchronization](https://shopify.dev/docs/apps/build/sales-channels/product-sync) guide. ***