# Shopify  ## What does the Shopify connection provide?  Start with the [Shopify quickstart](https://docs.gadget-canary.xyz/guides/plugins/shopify/quickstart) to connect Gadget and Shopify in a few minutes. * Gadget handles the OAuth process for merchants installing your application out of the box. See [Shopify OAuth](https://docs.gadget-canary.xyz/guides/plugins/shopify/advanced-topics/oauth) * All objects in Shopify's [GraphQL Admin API](https://shopify.dev/docs/api/admin-graphql) can be synced into your Gadget app as [models](https://docs.gadget-canary.xyz/guides/models). Connected models provide data access to any installing store's data, with no rate limit. Connected models can be extended in Gadget with additional fields to store extra information, though these fields will not sync back with Shopify by default. * Gadget automatically registers webhooks for all Shopify API scopes you care about, mapping the events to your models to keep data in sync by running [actions](https://docs.gadget-canary.xyz/guides/actions) when webhooks are received. Actions can be used to react to changes within Shopify, make API calls back to Shopify to update data, or do anything else you can do with JavaScript. * Gadget provides an easy-to-use API client for working with the Shopify API in backend code that manages rate limits and authentication. See * Missed webhooks from bugs or infrastructure issues are recovered using a daily background sync, as per Shopify's recommended [best practices](https://shopify.dev/apps/webhooks#best-practices). You can also manually sync your app to Shopify at any time or trigger syncs with the . * Historical data (old products, orders, customers, etc) can be easily synced when a shop installs your app * Gadget provides an [embedded Shopify Admin frontend](https://docs.gadget-canary.xyz/guides/plugins/shopify/frontends) out of the box, as soon as you connect to Shopify in your development environment * Gadget has easy-to-use facilities for [billing your merchants](https://docs.gadget-canary.xyz/guides/plugins/shopify/advanced-topics/billing) Currently, Gadget uses Shopify's `2026-01` API version for new connections. Shopify marked the REST Admin API as legacy on October 1, 2024. Gadget uses Shopify's GraphQL Admin API. ## Setting up the Shopify connection  If you are looking to connect Gadget with Shopify, follow the [Shopify quickstart](https://docs.gadget-canary.xyz/guides/plugins/shopify/quickstart). ## Available models  Gadget receives webhooks and syncs data from Shopify for the following models: | Model Name | Required Scopes | Webhook Topics | | --- | --- | --- | | App | Always available | non-webhook | | App Credit | Always available | non-webhook | | | Shopify App Credits (and other billing resources) are only available to OAuth apps created via the Shopify CLI or in the Shopify Partners dashboard that are marked for Public distribution. To successfully receive webhooks or sync this model, you must mark your app for Public distribution. Find more instructions in the [Shopify docs](https://shopify.dev/apps/distribution/select-distribution-method). | | | App Installation | Always available | non-webhook | | Company | `read_customers` | `companies/create`, `companies/update`, `companies/delete` | | | Shopify Company and its related models are only available for apps installed on Shopify Partners Plus stores. To successfully sync and receive webhooks for these models, you must have access to a Shopify Partners Plus store. Access to these models also grant you access to special fields in Shopify Order and Shopify Draft Order models that are only available for Plus stores only. Find more information in the [Shopify docs](https://help.shopify.com/en/manual/b2b/companies). | | | Company Contact | `read_customers` | `company_contacts/create`, `company_contacts/update`, `company_contacts/delete` | | Company Contact Role | `read_customers` | non-webhook | | Company Location | `read_customers` | `company_locations/create`, `company_locations/update`, `company_locations/delete` | | Company Address | `read_customers` | Sent within Company Location model | | Company Contact Role Assignment | `read_customers` | non-webhook | | Company Location Catalog | `read_customers` | non-webhook | | App Purchase One Time | Always available | `app_purchases_one_time/update` | | | Shopify App Purchase One Time (and other billing resources) are only available to OAuth apps created via the Shopify CLI or in the Shopify Partners dashboard that are marked for Public distribution. To successfully receive webhooks or sync this model, you must mark your app for Public distribution. Find more instructions in the [Shopify docs](https://shopify.dev/apps/distribution/select-distribution-method). | | | App Subscription | Always available | `app_subscriptions/update` | | | Shopify App Subscription (and other billing resources) are only available to OAuth apps created via the Shopify CLI or in the Shopify Partners dashboard that are marked for Public distribution. To successfully receive webhooks or sync this model, you must mark your app for Public distribution. Find more instructions in the [Shopify docs](https://shopify.dev/apps/distribution/select-distribution-method). | | | App Usage Record | Always available | non-webhook | | Blog | `read_content` | non-webhook | | | Shopify blogs support Metafields but do not provide a GraphQL mechanism for syncing them. Gadget does not support Metafields on Shopify Blog models. | | | Article | `read_content` | non-webhook | | | Shopify blog posts support Metafields but do not provide a REST or GraphQL mechanism for syncing them. Gadget does not support Metafields on Shopify Article models. | | | Comment | `read_content` | non-webhook | | Theme | `read_themes` | `themes/create`, `themes/update`, `themes/delete` | | | Gadget only syncs themes and assets that a store owns for each Shopify store, which excludes demo themes. Themes and assets for themes with `role: "demo"` in the Shopify API are not synced as API clients don't have permission to access individual asset values. | | | Asset | `read_themes` | non-webhook | | | Gadget syncs the [Theme](https://shopify.dev/docs/api/admin-graphql/latest/objects/OnlineStoreTheme) and[Asset](https://shopify.dev/docs/api/admin-graphql/latest/objects/OnlineStoreThemeFile) resources from Shopify using the GraphQL Admin API. Gadget does not sync the `value` column of the Asset resource. Syncing the `value` field can be done with a custom code effect, but Shopify requires one API call per asset to retrieve asset values, which often stresses Shopify API rate limits too much. Shopify Theme assets are often also quite large, including images and large JS files that are generally not important for applications to sync and store again.

Gadget recommends avoiding syncing the Asset and Theme model if possible, and instead making API calls directly to Shopify to work with assets using the [`connections.shopify.current`](https://docs.gadget-canary.xyz/guides/plugins/shopify#adding-code-effects) API client.
Gadget only syncs themes and assets that a store owns for each Shopify store, which excludes demo themes. Themes and assets for themes with `role: "demo"` in the Shopify API are not synced as API clients don't have permission to access individual asset values. | | | Balance Transaction | `read_shopify_payments_payouts` | non-webhook | | Checkout | `read_checkouts`, `read_orders` | `checkouts/create`, `checkouts/update`, `checkouts/delete` | | | The `read_checkouts` scope will allow Gadget to process checkout related webhooks, but does not provide a mechanism to sync all existing checkouts.

Adding the `read_orders` scope, while also including the Checkout model in your application, will result in all [Abandoned Checkouts](https://shopify.dev/docs/api/admin-graphql/latest/objects/AbandonedCheckout) that aren't handled via webhooks (failed delivery, checkouts that already exist) being imported during nightly or manual syncs. | | | Billing Address | `read_checkouts`, `read_orders` | Sent within Checkout model | | Checkout Applied Gift Card | `read_checkouts`, `read_orders` | non-webhook | | Checkout Line Item | `read_checkouts`, `read_orders` | Sent within Checkout model | | Checkout Shipping Rate | `read_checkouts`, `read_orders` | non-webhook | | Shipping Address | `read_checkouts`, `read_orders` | Sent within Checkout model | | Bulk Operation | Always available | `bulk_operations/finish` | | Carrier Service | `read_shipping` | non-webhook | | Cart | `read_orders` | `carts/create`, `carts/update` | | Cart Line Item | `read_orders` | Sent within Cart model | | Catalog | `read_products` | non-webhook | | Collection | `read_products` | `collections/create`, `collections/update`, `collections/delete` | | | Gadget uses the Collection model to represent both Custom Collections and Smart Collections from Shopify. | | | Collect | `read_products` | non-webhook | | Country | `read_shipping` | non-webhook | | Province | `read_shipping` | non-webhook | | Customer | `read_customers` | `customers/create`, `customers/update`, `customers/delete` | | Customer Address | `read_customers` | Sent within Customer model | | Customer Mergeable | `read_customers` | non-webhook | | Store Credit Account | `read_customers` | non-webhook | | Customer Payment Method | `read_customer_payment_methods` | `customer_payment_methods/create`, `customer_payment_methods/update`, `customer_payment_methods/revoke` | | Discount Code | `read_discounts` | non-webhook | | Discount | `read_discounts` | `discounts/create`, `discounts/update`, `discounts/delete` | | Discount Redeem Code | `read_discounts` | non-webhook | | Discount Customer Gets Product | `read_discounts` | non-webhook | | Discount Customer Buys Product | `read_discounts` | non-webhook | | Discount Customer Gets Product Variant | `read_discounts` | non-webhook | | Discount Customer Buys Product Variant | `read_discounts` | non-webhook | | Discount Customer Gets Collection | `read_discounts` | non-webhook | | Discount Customer Buys Collection | `read_discounts` | non-webhook | | Dispute | `read_shopify_payments_disputes` | `disputes/create`, `disputes/update` | | | Dispute data is only available for Shopify merchants using Shopify Payments. | | | Dispute Evidence | `read_shopify_payments_disputes` | non-webhook | | Dispute File Upload | `read_shopify_payments_disputes` | non-webhook | | Dispute Evidence Fulfillment | `read_shopify_payments_disputes` | non-webhook | | Domain | Always available | `domains/create`, `domains/update`, `domains/destroy` | | | Shopify Domain delete and update webhooks are missing key data, so Gadget does an inline sync of the Domain data via the \[GraphQL Admin API\](https://shopify.dev/docs/api/admin-graphql/latest/objects/Domain) when these webhooks arrive to properly discover updates and deletes. | | | Draft Order | `read_draft_orders` | `draft_orders/create`, `draft_orders/update`, `draft_orders/delete` | | Draft Order Line Item | `read_draft_orders` | Sent within Draft Order model | | Draft Order Platform Discount | `read_draft_orders` | non-webhook | | Draft Order Platform Discount Allocation | `read_draft_orders` | non-webhook | | Order | `read_orders` | `orders/create`, `orders/updated`, `orders/delete`, `orders/risk_assessment_changed` | | Duty | `read_orders` | Sent within Order Line Item model | | Fulfillment Event | `read_orders` | `fulfillment_events/create`, `fulfillment_events/delete` | | Fulfillment | `read_orders` | `fulfillments/create`, `fulfillments/update` | | Fulfillment Line Item | `read_orders` | Sent within Fulfillment model | | Order Adjustment | `read_orders` | Sent within Refund model | | Order Line Item | `read_orders` | Sent within Order model | | Order Risk | `read_orders` | non-webhook | | Order Transaction | `read_orders` | `order_transactions/create` | | Refund Duty | `read_orders` | Sent within Refund model | | Refund | `read_orders` | `refunds/create` | | Refund Line Item | `read_orders` | Sent within Refund model | | Return | `read_orders` | `returns/approve`, `returns/cancel`, `returns/close`, `returns/decline`, `returns/process`, `returns/reopen`, `returns/request`, `returns/update` | | Return Shipping Fee | `read_orders` | Sent within Return model | | Exchange Line Item | `read_orders` | Sent within Return model | | Reverse Fulfillment Order | `read_orders` | non-webhook | | Reverse Fulfillment Order Line Item | `read_orders` | non-webhook | | Reverse Fulfillment Order Disposition | `read_orders` | non-webhook | | Reverse Delivery | `read_orders` | non-webhook | | Reverse Delivery Line Item | `read_orders` | non-webhook | | Return Line Item | `read_orders` | Sent within Return model | | Shipping Line | `read_orders` | Sent within Order model | | Event | `read_content`, `read_products`, `read_price_rules`, `read_orders` | non-webhook | | File | `read_files`, `read_themes`, `read_products` | non-webhook | | | This model tracks files uploaded by the merchant in the Files section of the Shopify Admin. Shopify doesn't expose webhooks for the File resource, so files are only updated in Gadget on sync. Files can be accessed via the `read_files` or `read_themes` scopes. | | | Fulfillment Order | `read_assigned_fulfillment_orders`, `read_merchant_managed_fulfillment_orders`, `read_third_party_fulfillment_orders` | `fulfillment_orders/order_routing_complete`, `fulfillment_orders/fulfillment_request_submitted`, `fulfillment_orders/fulfillment_request_accepted`, `fulfillment_orders/fulfillment_request_rejected`, `fulfillment_orders/placed_on_hold`, `fulfillment_orders/cancellation_request_submitted`, `fulfillment_orders/cancellation_request_accepted`, `fulfillment_orders/cancellation_request_rejected`, `fulfillment_orders/cancelled`, `fulfillment_orders/fulfillment_service_failed_to_complete`, `fulfillment_orders/moved`, `fulfillment_orders/split`, `fulfillment_orders/merged`, `fulfillment_orders/hold_released`, `fulfillment_orders/line_items_prepared_for_local_delivery`, `fulfillment_orders/line_items_prepared_for_pickup`, `fulfillment_orders/rescheduled`, `fulfillment_orders/scheduled_fulfillment_order_ready` | | Fulfillment Order Line Item | `read_assigned_fulfillment_orders`, `read_merchant_managed_fulfillment_orders`, `read_third_party_fulfillment_orders` | non-webhook | | Fulfillment Order Destination | `read_assigned_fulfillment_orders`, `read_merchant_managed_fulfillment_orders`, `read_third_party_fulfillment_orders` | non-webhook | | Delivery Method | `read_assigned_fulfillment_orders`, `read_merchant_managed_fulfillment_orders`, `read_third_party_fulfillment_orders` | non-webhook | | Fulfillment Order Merchant Request | `read_assigned_fulfillment_orders`, `read_merchant_managed_fulfillment_orders`, `read_third_party_fulfillment_orders` | non-webhook | | Fulfillment Hold | `read_assigned_fulfillment_orders`, `read_merchant_managed_fulfillment_orders`, `read_third_party_fulfillment_orders` | non-webhook | | Fulfillment Service | `read_fulfillments` | non-webhook | | GDPR Request | Always available | `shop/redact`, `customers/redact`, `customers/data_request` | | | This model tracks incoming GDPR webhook requests from Shopify to delete merchant or customer data. These GDPR webhooks are required to be supported by Public applications for the Shopify app store, which you can read more about in the [Shopify Docs](https://shopify.dev/apps/webhooks/configuration/mandatory-webhooks). This model doesn't correspond to an API endpoint within Shopify. | | | Gift Card | `read_gift_cards` | non-webhook | | | This model tracks issued gift cards for a Shopify store. Shopify only allows access to the `read_gift_cards` scope on Shopify Plus stores, and only for [Custom apps](https://help.shopify.com/en/manual/apps/app-types/custom-apps) provisioned within the Shopify Admin.

Shopify doesn't offer gift card webhooks, so gift card data is only refreshed on sync. | | | Inventory Item | `read_inventory` | `inventory_items/create`, `inventory_items/update`, `inventory_items/delete` | | | Inventory Items are synced using the [GraphQL API](https://shopify.dev/docs/api/admin-graphql/latest/objects/InventoryLevel) for Inventory Levels for each Location to get a list of inventory levels, and then using the [GraphQL API](https://shopify.dev/docs/api/admin-graphql/latest/objects/InventoryItem) to get a list of inventory items for each level. | | | Inventory Level | `read_inventory` | `inventory_levels/connect`, `inventory_levels/update`, `inventory_levels/disconnect` | | | Inventory Levels are synced using the [GraphQL API](https://shopify.dev/docs/api/admin-graphql/latest/objects/InventoryLevel) endpoint for Inventory Levels at a Location. | | | Inventory Quantity | `read_inventory` | non-webhook | | Location | `read_locations` | `locations/create`, `locations/update`, `locations/activate`, `locations/deactivate`, `locations/delete` | | Market | `read_markets` | `markets/create`, `markets/update`, `markets/delete` | | Market Region | `read_markets` | non-webhook | | Market Web Presence | `read_markets` | non-webhook | | Market Catalog | `read_markets` | non-webhook | | Page | `read_content` | non-webhook | | | Shopify pages support Metafields but do not provide a GraphQL mechanism for syncing them. Gadget does not support Metafields on Shopify Page models. | | | Payment Terms | `read_orders`, `read_draft_orders` | `payment_terms/create`, `payment_terms/update`, `payment_terms/delete` | | Payment Schedule | `read_orders`, `read_draft_orders` | Sent within Payment Terms model | | Payout | `read_shopify_payments_payouts` | non-webhook | | Price Rule | `read_price_rules` | non-webhook | | Price List | `read_products` | non-webhook | | Price List Price | `read_products` | non-webhook | | Quantity Price Break | `read_products` | non-webhook | | Product | `read_products` | `products/create`, `products/update`, `products/delete` | | Product Image | `read_products` | Sent within Product model | | | This model has been deprecated. Read more about [ProductImage deprecation](https://docs.gadget-canary.xyz/guides/plugins/shopify#deprecated-shopifyproductimage-model). | | | Product Media | `read_products` | Sent within Product model | | Product Option | `read_products` | Sent within Product model | | Product Variant | `read_products` | Sent within Product model | | Product Variant Media | `read_products` | Sent within Product Variant model | | Redirect | `read_content` | non-webhook | | Return Reason Definition | `read_returns` | non-webhook | | Script Tag | `read_script_tags` | non-webhook | | Shop | Always available | `shop/update`, `app/uninstalled` | | Segment | `read_customers` | `segments/create`, `segments/update`, `segments/delete` | | Selling Plan Group | `read_products` | `selling_plan_groups/create`, `selling_plan_groups/update`, `selling_plan_groups/delete` | | Selling Plan Group Product | `read_products` | Sent within Selling Plan Group model | | Selling Plan Group Product Variant | `read_products` | Sent within Selling Plan Group model | | Selling Plan | `read_products` | Sent within Selling Plan Group model | | Subscription Contract | `read_own_subscription_contracts` | `subscription_contracts/create`, `subscription_contracts/update` | | Subscription Billing Attempt | `read_own_subscription_contracts` | `subscription_billing_attempts/success`, `subscription_billing_attempts/failure`, `subscription_billing_attempts/challenged` | | Subscription Line | `read_own_subscription_contracts` | non-webhook | | Subscription Manual Discount | `read_own_subscription_contracts` | non-webhook | | Tender Transaction | `read_orders` | `tender_transactions/create` | | Business Entity | Always available | non-webhook | | Payments Account | `read_shopify_payments_accounts` | non-webhook | ### Deprecated `shopifyProductImage` model  [Product image](https://shopify.dev/docs/api/admin-rest/latest/resources/product-image) records in Shopify no longer belong to a single product, a single image can be referenced by multiple products. Because of this change, syncing `shopifyProductImage` data in Gadget may result in unexpected and non-deterministic updates to the relationship field on `shopifyProductImage` records when an image is shared between multiple products. To address this, Gadget has introduced the `shopifyProductMedia` and `shopifyProductVariantMedia` models that sync all product and product variant related media including images, videos, 3D models, and files. We recommend using `shopifyProductMedia` and `shopifyProductVariantMedia` models and have marked `shopifyProductImage` as deprecated. If you have any questions, please get in touch with the Gadget staff on [Discord](https://ggt.link/discord). ### Field Type Changes During API Version Upgrades  When upgrading to a new Shopify API version, particularly version `2025-04`, which migrates all Shopify models to sync via Shopify's GraphQL Admin API, you may encounter field type changes on existing Shopify models. In these cases, the field's value in your model's data table will be **nulled**, and **Gadget will preserve the existing data** by automatically creating a **backup field**. This backup field follows the naming format: `[fieldApiIdentifier]Backup`. If your app relies on the data in a field that has changed types, Gadget recommends the following: * **Update your code** to use a nullish coalescing fallback (`??`) to fall back to the backup field when the new field is null ```js const status = record.status ?? record.statusBackup; ``` * You may want to run a [force sync](https://docs.gadget-canary.xyz/guides/plugins/shopify/syncing-shopify-data#forced-syncs) to backfill the data and populate the field with values in the new format Also keep in mind that the values themselves may differ after a type change. For example, a `status` field previously stored as a `string` with value `active` may now be an `enum` with value `ACTIVE`. ### Shop Plan Changes  If a previously non-active shop has changed plans (e.g. going from `frozen` to `basic` when a frozen shop is re-activated by Shopify), you may want to run a [force sync](https://docs.gadget-canary.xyz/guides/plugins/shopify/syncing-shopify-data#forced-syncs) to update data records for your app's Shopify models that were missed while the shop was on the old plan. ## Troubleshooting  The sync between Shopify and my Gadget app failed. Do you have any custom code running within the `create` action on the Shopify Sync model? A custom action with errors will prevent the sync from completing successfully. Try removing or editing the code within actions and running a manual sync. Actions aren't updating my records in Shopify. You need to explicitly call out to the Shopify client in your action to manipulate data in Shopify. See [accessing the Shopify API](https://docs.gadget-canary.xyz/guides/plugins/shopify). Webhooks aren't being registered with Shopify. If you see that some webhooks aren't being registered even after clicking **Register Webhooks** on the **Installs** page, it is possible that your app has not requested access to **Protected Customer Data**. See for more details. ## Frequently asked questions  How do I make calls back to Gadget from a Shopify storefront? You can call your Gadget app's API client in the storefront's Shopify theme using [Shopify app proxies](https://docs.gadget-canary.xyz/guides/plugins/shopify/building-shopify-apps#storefront-requests-using-shopify-app-proxies). How do I make an [embedded Shopify app](https://shopify.dev/apps/getting-started/app-types#embedded-apps) using Gadget? Gadget supports being used as a backend for an app embedded in the Shopify Admin. You can read the docs for setting up the [Gadget app client and Provider](https://docs.gadget-canary.xyz/api/example-app/development/public-embedded-shopify-apps) and go through the [tutorial](https://docs.gadget-canary.xyz/guides/plugins/shopify/quickstart) that walks you through setting up an embedded app with the Shopify CLI and Gadget. When does Gadget automatically sync my Shopify connection? Gadget will fully sync data from stores connected to your environment when you hit the "Sync" button on the **Installs** page, when you run the sync API from the API playground, or from within your application's logic. Gadget will also automatically fetch all recent data from Shopify in the background in a scheduled daily sync. To see when your app was last synced with Shopify, you can view the Shopify Sync data editor, or check the **Installs** page through the left nav or by navigating to **Settings** -> **Plugins** -> **Shopify** -> **Installs**. ## Building Shopify apps using the Gadget connection  For more information on how to build Shopify apps using the Gadget Shopify connection, check out our guide [here](https://docs.gadget-canary.xyz/guides/plugins/shopify/building-shopify-apps).