---
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
api_name: admin
---

# 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.

Product feed output is resolved from the intersection of your channel's publishing controls and the relevant [channel market](https://shopify.dev/docs/apps/build/markets#channel-markets) catalog, pricing, and currency configuration.

***

## What you'll learn

In this tutorial, you'll learn how to do the following tasks:

* Describe your channel to Shopify
* Establish a new channel connection
* Subscribe to product feed webhooks
* Initiate a full sync

***

## Requirements

* Your app has the `read_product_listings` [access scope](https://shopify.dev/docs/api/usage/access-scopes#read_product_listings). Learn more about requesting access scopes when your app is installed using [authorization code grant](https://shopify.dev/docs/apps/build/authentication-authorization/authenticate-standalone-apps).

***

## Step 1: Describe your channel

When you create a sales channel, you need to describe its properties to Shopify.

Channel descriptions include capabilities, categories, fields, validation rules, and more. This channel description enables Shopify to automatically manage a large part of the merchant's channel experience.

Describing a channel requires the following steps:

1. Add the [channel config extension](https://shopify.dev/docs/apps/build/sales-channels/channel-config-extension) to your app
2. Create a channel specification `example-us-channel.toml` for that channel

**Note:**

A sales channel app can have many target channels. Each target channel can have a different taxonomy, product type set, and field definitions. See [Channel config extension](https://shopify.dev/docs/apps/build/sales-channels/channel-config-extension#channel-specifications) for more details.

## example-us-channel.toml

```toml
handle = "example-us"
label = "example.com"
icon = "example-channel-icon.svg"
productFeedManagement = "automatic"


[capabilities]
bundles = true
digitalProducts = true


[requirements]
expectsOnlineStoreParity = false
merchantOfRecord = "channel"


[[countries]]
code = "US"
languages = [
"en"
]
currency = "USD"
```

Then deploy your channel targets with Shopify CLI:

## Terminal

```terminal
shopify app deploy
```

***

## Step 2: Establish channel connections

Before subscribing to product feeds, you need to establish at least one channel connection. Channel connections link your channel specification to a merchant's external account and automatically create the product feeds for the channel's configured regions.

See [Managing channel connections](https://shopify.dev/docs/apps/build/sales-channels/channel-connections) for the full guide on creating, updating, and querying channel connections using the [`channelCreate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/channelCreate), [`channelUpdate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/channelUpdate), [`channelDelete`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/channelDelete) mutations and `channels` query.

***

## Step 3: Subscribe to product feed webhooks

Subscribe to product feed webhooks to receive feed status changes, initial [full sync payloads](#step-5-initiate-a-full-sync), [incremental sync events](#subscribe-to-incremental-sync-events), and full sync completion signals. Market or catalog changes can also cause feed update or reconciliation events.

**Note:**

If you set `productFeedManagement` to `"automatic"` (recommended) in your channel specification, product feeds are created automatically when channel connections are established. If you set it to `"manual"`, you'll need to create feeds using the `productFeedCreate` mutation before initiating a full sync. See [Step 4](#step-4-optional-create-feeds-manually) below.

### 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-5-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.
* Channel market catalog, pricing, or currency configuration requires product-level reconciliation.
* Products are published to the app.
* Products are unpublished from the 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 a market configuration. For example, if a merchant unpublishes a language, then dependent feeds become inactive. Catalog, pricing, or currency changes on channel or region markets can also require feed reconciliation. 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"
}
```

### Store availability changes

When a merchant changes their local pickup or store transfer settings (turning on store transfers for a pickup location, changing which locations a pickup location can transfer inventory from, adding or removing a transfer exclusion, or changing a pickup location's processing time), Shopify automatically triggers a full product feed sync for the shop. This keeps the `storeAvailability` data in your feed (including each variant's pickup `available` flag and `pickUpTime`) current with the merchant's store-transfer availability. These syncs are coalesced, so several rapid changes result in a single sync.

A variant can be available for pickup at a location even when it isn't in stock there, if the merchant has set up a store transfer that supplies it from another location. In that case its `pickUpTime` reflects an adjusted estimated pickup time that accounts for the transfer, rather than the location's standard pickup processing time.

***

## Step 4 (Optional): Create feeds manually

If you set `productFeedManagement` to `"manual"` in your channel specification, you need to create product feeds for each country and language pair before initiating a full sync.

**Note:**

Skip this step if your channel specification uses `productFeedManagement = "automatic"`. Feeds are created automatically when you establish a channel connection.

Use the `productFeedCreate` mutation to create a feed:

## Create a product feed

```graphql
mutation {
  productFeedCreate(input: {
    country: US
    language: EN
  }) {
    productFeed {
      id
      country
      language
      status
    }
    userErrors {
      field
      message
    }
  }
}
```

Repeat this for each country and language pair defined in your channel specification.

***

## Step 5: Initiate a full sync

After your app has subscribed to the necessary webhook topics, trigger a full sync to fetch the merchant's published catalog.

Use the full sync mutation that matches how your channel manages feeds:

* If your channel uses `productFeedManagement = "automatic"`, use [`channelFullSync`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/channelFullSync) to trigger a full sync for a channel connection. Shopify syncs all relevant feeds for that channel, or the feeds matching the optional `country` and `language` filters. See [Managing channel connections](https://shopify.dev/docs/apps/build/sales-channels/channel-connections#trigger-a-full-sync-for-a-channel).
* If your channel uses `productFeedManagement = "manual"`, use [`productFullSync`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productFullSync) to trigger a full sync for each individual product feed that you created in [Step 4](#step-4-optional-create-feeds-manually). The returned [`id`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productFullSync#field-productfullsyncpayload-id) 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#implement-reconciliation-jobs).

When a full sync is initiated, Shopify fires individual [`PRODUCT_FEEDS_FULL_SYNC` webhook payloads](#subscribe-to-full-sync-events) for each of the merchant's published products. The webhook payloads contain localized data and market-specific catalog, pricing, and currency data according to the feed's context. Shopify sends a confirmation event to the [`PRODUCT_FEEDS_FULL_SYNC_FINISH` subscription](#optional-subscribe-to-full-sync-completion-events) after the full sync is complete. The payload's `fullSyncId` identifies the sync operation.

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 feed-scoped 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": []
    }
  }
}
```

### Full sync by download (Optional)

**Feature preview:**

Full sync by download is in an invite-only feature 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/apps/build/apis/graphql-admin/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?<params />bulk-4258087174166.jsonl&response-content-type=application%2Fjsonl"
  }
}
```

***

## Delivery option estimates

Product feed payloads can include a `deliveryOptionEstimates` field on each product variant. It gives a low-fidelity view of the delivery options available for a variant, contextualized to the feed's country, so you can surface shipping options to buyers without interpreting a merchant's shipping configuration.

Estimates are grouped into `regionalEstimates` by `subdivisionCodes` (provinces or states), because options can differ within a country. If `subdivisionCodes` is an empty array, then the estimates apply country-wide.

Every estimate includes:

* `deliveryMethodType`: The delivery method type. This is `SHIPPING` for all estimates.
* `description`: An optional description of the delivery option.
* `freeMinimumOrderAmount`: The minimum order total required to qualify for the option for free, if the merchant configured one.

Static-rate options also include:

* `title`: The name of the delivery option, aggregated from shipping options that share a name.
* `priceRange`: The `min` and `max` price across the matching shipping options.
* `configuredDeliveryDaysRange`: The combined processing and transit time as a range of calendar days (`min` and `max`).

Dynamic-rate options (rates calculated by a carrier at checkout) instead include:

* `carrierServiceName`: The name of the carrier service.
* `activeServiceNames`: The carrier service options the merchant enabled for this delivery method.

Estimates refresh through product feed webhooks (both incremental and full sync) as a merchant updates their delivery configuration. Compare `estimatesDigest` against the value you last received to detect which variants changed, and reconcile through your [regular reconciliation syncs](#step-5-initiate-a-full-sync) as you would for any other product data.

The `deliveryOptionEstimates` field appears on each variant node in the full sync and incremental sync payloads:

## deliveryOptionEstimates on a variant

```json
{
  "deliveryOptionEstimates": {
    "estimatesDigest": "f72c0bbfc467ab7eb76f7a69d2844fa100f912f9",
    "regionalEstimates": [
      {
        "subdivisionCodes": ["FL", "OR"],
        "estimates": [
          {
            "deliveryMethodType": "SHIPPING",
            "title": "Standard Shipping",
            "description": null,
            "freeMinimumOrderAmount": {
              "amount": "50.00",
              "currencyCode": "USD"
            },
            "configuredDeliveryDaysRange": {
              "min": 3,
              "max": 5
            },
            "priceRange": {
              "min": {
                "amount": "5.00",
                "currencyCode": "USD"
              },
              "max": {
                "amount": "10.00",
                "currencyCode": "USD"
              }
            }
          },
          {
            "deliveryMethodType": "SHIPPING",
            "description": "Rate calculated at checkout by carrier",
            "freeMinimumOrderAmount": null,
            "carrierServiceName": "USPS",
            "activeServiceNames": ["Ground", "First-Class"]
          }
        ]
      },
      {
        "subdivisionCodes": ["NY", "NJ"],
        "estimates": [
          {
            "deliveryMethodType": "SHIPPING",
            "description": "Rate calculated at checkout by carrier",
            "freeMinimumOrderAmount": null,
            "carrierServiceName": "USPS",
            "activeServiceNames": ["Priority Mail", "Priority Mail Express"]
          }
        ]
      }
    ]
  }
}
```

***

## Next steps

* For other channel product sync solutions, refer to the [Options to sync product data](https://shopify.dev/docs/apps/build/sales-channels/product-sync) guide.

***
