Syncing
The syncing system for the Shopify Connection works by leveraging actions. Each time Gadget goes to sync the connected shop, Gadget creates a new Shopify Sync record and dispatches the Run and either the Complete or Error Actions based on the outcome of the sync. There are three types of syncs for the Shopify connection: API-triggered, Manual, and Daily.
Daily syncs
The Shopify connection will automatically fetch all recent data in the background daily. This ensures we have all of the shop's data in the case of a missing webhook or for resources that don't have webhooks (such as the Online Store content). This sync only fetches data from the past day, so if the sync fails or has scheduling issues, there's a possibility you may not have the most up-to-date data in Gadget. If this does occur, check the Shopify Sync data viewer or logs via the Sync History to identify any errors.
API-triggered Syncs
An API-triggered sync can be initiated via the GraphQL API, JS clients, or the api
object in the actions code. There are many ways that you can use API-triggered syncs. The three main ways are after a shop installation, on a model's action, and syncing data within a specified date range.
Avoid adding an API-triggered sync to the shopifySync
model's actions! This will cause an infinite loop of syncs. Instead, we recommend
adding API-triggered syncs to the shopifyShop
model's install
action to sync store data on app installation, or using a custom action
to trigger data syncs.
Sync on shop install
It may be necessary to run an initial sync upon installation. For example, you may want to display all products on a user interface immediately after a merchant installs your app. To do this, use the onSuccess
function on the Shopify Shop model's install
action with the following code:
1export async function onSuccess({ api, record, params, logger, connections }) {2 await api.shopifySync.run({3 shopifySync: {4 domain: record.domain,5 shop: {6 _link: record.id,7 },8 },9 });10}
1mutation ($shopifySync: RunShopifySyncInput) {2 runShopifySync(shopifySync: $shopifySync) {3 success4 errors {5 message6 ... on InvalidRecordError {7 validationErrors {8 apiIdentifier9 message10 }11 record12 model {13 apiIdentifier14 }15 }16 }17 shopifySync {18 __typename19 id20 state21 createdAt22 domain23 errorDetails24 errorMessage25 models26 syncSince27 updatedAt28 }29 }30}
{ "shopifySync": { "shop": { "_link": "SHOPID" }, "domain": "SHOPDOMAIN" } }
Sync by model
You can optionally pass a models
parameter to the runSync
call to specify which particular Shopify models to sync. For example, if you wish to sync all products
in your backend, but not orders
, the models
should be passed as an array of model
API identifiers. If the models
parameter is omitted or is an empty array, all Shopify models will be synced.
1export async function onSuccess({ api, record, params, logger, connections }) {2 if (record.shopId) {3 const shop = await api.shopifyShop.findOne(record.shopId);4 await api.shopifySync.run({5 shopifySync: {6 domain: shop.domain,7 shop: {8 _link: record.shopId,9 },10 // optional parameter11 models: ["shopifyProduct"],12 },13 });14 }15}
1mutation ($shopifySync: RunShopifySyncInput) {2 runShopifySync(shopifySync: $shopifySync) {3 success4 errors {5 message6 ... on InvalidRecordError {7 validationErrors {8 apiIdentifier9 message10 }11 record12 model {13 apiIdentifier14 }15 }16 }17 shopifySync {18 __typename19 id20 state21 createdAt22 domain23 errorDetails24 errorMessage25 models26 syncSince27 updatedAt28 }29 }30}
1{2 "shopifySync": {3 "shop": { "_link": "SHOPID" },4 "domain": "SHOPDOMAIN",5 "models": ["shopifyProduct"]6 }7}
Sync by date-time
You may want to limit syncing to recent records. To do this, you can pass the syncSince
parameter to indicate which records Gadget should sync. Gadget will check the shopifyUpdateAt
field for each Shopify record, and only sync records created or updated within the specified window. Without this parameter, the API-triggered sync will copy every record to Gadget, regardless of when it was created or updated.
1export async function onSuccess({ api, record, params, logger, connections }) {2 if (record.shopId) {3 const shop = await api.shopifyShop.findOne(record.shopId);4 const syncSince = new Date();5 const millisecondsPerDay = 1000 * 60 * 60 * 24;6 // sync from 5 days ago7 syncSince.setTime(syncSince.getTime() - 5 * millisecondsPerDay);8 await api.shopifySync.run({9 shopifySync: {10 domain: shop.domain,11 // optional parameter12 syncSince,13 shop: {14 _link: record.shopId,15 },16 },17 });18 }19}
1mutation ($shopifySync: RunShopifySyncInput) {2 runShopifySync(shopifySync: $shopifySync) {3 success4 errors {5 message6 ... on InvalidRecordError {7 validationErrors {8 apiIdentifier9 message10 }11 record12 model {13 apiIdentifier14 }15 }16 }17 shopifySync {18 __typename19 id20 state21 createdAt22 domain23 errorDetails24 errorMessage25 models26 syncSince27 updatedAt28 }29 }30}
1{2 "shopifySync": {3 "shop": { "_link": "SHOPID" },4 "domain": "SHOPDOMAIN",5 "syncSince": "ISOSTRING"6 }7}
Forced syncs
A sync will not update any fields or run any actions if the shopifyUpdatedAt
value in the payload is less than or equal to the shopifyUpdatedAt
value currently stored on the record. There are certain situations, such as Shopify API version upgrades, where you may want to backfill new values or re-run your actions on existing records. In these situations, you can run a sync with the force
field set to true
. A forced sync will update all values and re-run all actions on applicable records.
After connecting to Shopify, a globalShopifySync
global action has been added to your Gadget app. You can use this global action to run a forced sync across all stores your app has been installed on. If you go to Global Actions, then select the globalShopifySync action and click Run Action, you will be brought to the API Playground with the globalShopifySync
mutation loaded for you. To run a forced sync, you need to include "force": true
in your variables, as well as the API Key/Client Id of your connected Shopify app. You can also include syncSince and models variables to limit the scope of the sync.
Variables for the globalShopifySync mutationjson{"apiKeys": ["<your Shopify app's API key>"],"force": true}
You can also run a forced sync on an individual store in JS or GraphQL by writing a custom action:
1export async function run({ api, record, params, logger, connections }) {2 if (record.shopId) {3 const shop = await api.shopifyShop.findOne(record.shopId);4 const syncSince = new Date();5 const millisecondsPerDay = 1000 * 60 * 60 * 24;6 // sync from 5 days ago7 syncSince.setTime(syncSince.getTime() - 5 * millisecondsPerDay);8 await api.shopifySync.run({9 shopifySync: {10 domain: shop.domain,11 // optional parameter12 syncSince,13 shop: {14 _link: record.shopId,15 },16 force: true,17 },18 });19 }20}
1mutation ($shopifySync: RunShopifySyncInput) {2 runShopifySync(shopifySync: $shopifySync) {3 success4 errors {5 message6 ... on InvalidRecordError {7 validationErrors {8 apiIdentifier9 message10 }11 record12 model {13 apiIdentifier14 }15 }16 }17 shopifySync {18 __typename19 id20 state21 createdAt22 domain23 errorDetails24 errorMessage25 models26 syncSince27 updatedAt28 }29 }30}
1{2 "shopifySync": {3 "shop": { "_link": "SHOPID" },4 "domain": "SHOPDOMAIN",5 "syncSince": "ISOSTRING",6 "force": true7 }8}
Manual syncs
A sync will be queued (without a syncSince
set) when clicking the "Sync" button next to your Shopify Connection. This will make a call to the Shopify Sync model's Run Action.

This sync may take some time to complete, depending on how much data the shop has.
Sync only fields
A small number of fields on Shopify models are sync only. This means that they are not created or updated on webhooks, but are set only during a sync. To handle changes to these fields you can detect the trigger type in your onSuccess
function by checking the trigger
property in the action context.
JavaScript1export async function onSuccess({2 api,3 record,4 params,5 logger,6 connections,7 trigger,8}) {9 if (trigger.type == "shopify_sync") {10 // do something with a sync only field11 }12}
The current list of sync only fields are:
Model Name | Fields |
---|---|
Shopify Customer | Mergeable Statistics |
Shopify Draft Order | Purchasing Entity |
Shopify Order | Additional Fees Purchasing Entity |
Shopify Product | Product Category |
Sync error handling
If an error occurs during a sync, the Shopify Sync model's Error action will be called. For example, here's how you teach Gadget to alert you via SMS when a sync fails.
JavaScript1const Twilio = require("twilio");2// Environment variables set in Gadget3const twilio = new Twilio(4 process.env["TWILIO_ACCOUNT_SID"],5 process.env["TWILIO_ACCOUNT_AUTH_TOKEN"]6);78export async function run({ api, record, params, logger, connections }) {9 /*10 params: {11 id: string;12 shopifySync: {13 errorMessage: string;14 errorDetails: string;15 }16 }17 */1819 const errors = params.shopifySync.errorDetails.split("\n");2021 // send the errors individually to the logger22 for (const error of errors) {23 logger.error({ error }, "an error occurred syncing");24 }2526 // send an SMS notification27 await twilio.sendSMS({28 to: "+11112223333",29 from: "+11112223333",30 body: `Shopify sync failed with message: ${params.shopifySync.errorMessage}`,31 });32}
id
is the Shopify Sync id that was createderrorMessage
contains a generate description of the error or errorserrorDetails
contains a all error messages (if more than 1 occurred) joined by a\n
The best way to debug why a sync may have failed is to search for errors through your logs. Use the following log query to filter by sync ID (replace {syncId}
with ID of the sync that has failed):
{app_logs_service=~".+", environment_id="{environmentID}"} | json | connectionSyncId = "{syncID}"