I am performing a Bulk Operation for Orders, Customers and AbandonedCheckouts that have been updated within a date range.
The bulker operation query works fine and I can download the JSONL files.
The date range query filter works fine for the Customers and Orders, but not for the AbandonedCheckouts. I get records returned for Customers and Orders, but 0 records returned for AbandonedCheckouts when I know records exist during that timeframe as I have tested getting all the records and can see them.
My GraphQL Query (in Dotnet C#) for AbandonedCheckouts inside the bulk operation query is:
abandonedCheckouts(query:\"updated_at:>=" + fromDT + " AND updated_at:<" + toDT + "\") { edges { node { id billingAddress { city country countryCodeV2 province provinceCode zip } completedAt createdAt customer { id firstName email } updatedAt totalPriceSet { shopMoney { amount currencyCode } } } } }
which returns 0 records regardless of the DateTime variable values for fromDT and toDT.
If I change the filter to first or last it returns ALL of the AbandonedCheckouts for the shop, not just first x or last x.
As I mentioned, this exact same filter and query structure works for Customers and Orders so Iâm wonderiing if there is something different about AbandonedCheckouts that I am missing?
If you test your query outside of the bulk operations does it work? I would recommend testing that and adding in the syntax debugging headers to get a clear view of how itâs being parsed.
Thanks @KyleG-Shopify . Iâm not sure how to add that header using ShopifySharp.GraphQL but Iâm getting the following responses for the following queries outside the bulk operation:
abandonedCheckouts(first: 10) { edges { node { id billingAddress { city country countryCodeV2 province provinceCode zip } completedAt createdAt customer { id firstName email } updatedAt totalPriceSet { shopMoney { amount currencyCode } } } } }
Response: syntax error, unexpected IDENTIFIER (âabandonedCheckoutsâ) at [1, 1]
query AbandonedCheckouts { abandonedCheckouts(first: 10) { edges { node { id billingAddress { city country countryCodeV2 province provinceCode zip } completedAt createdAt customer { id firstName email } updatedAt totalPriceSet { shopMoney { amount currencyCode } } } } } }
Response: It gives me the first 10 as expected
NOTE: If I try to do this query (2) in the bulk operation it fails with
Graph user errors were returned: System.Collections.Generic.List1[System.String]: Invalid bulk query: Field 'query' doesn't exist on type 'QueryRoot' System.Collections.Generic.List1[System.String]: Invalid bulk query: Field âAbandonedCheckoutsâ doesnât exist on type âQueryRootâ
but query (1) works but it returns ALL historical AbandonedCheckouts not just the first 10. Seems like either the filter should work or it should allow the additional âquery AbandonedCheckouts {â in the bulk operation.
Noting also that for Orders and Customers the filter works fine in the format of (1) above.
query AbandonedCheckouts { abandonedCheckouts(query:"updated_at:>=" + fromDT + " AND updated_at:<" + toDT + "") { edges { node { id billingAddress { city country countryCodeV2 province provinceCode zip } completedAt createdAt customer { id firstName email } updatedAt totalPriceSet { shopMoney { amount currencyCode } } } } } }
Response: Graph errors were returned: you must provide one of first or last
query AbandonedCheckouts { abandonedCheckouts(first: 10, query:"updated_at:>=" + fromDT + " AND updated_at:<" + toDT + "") { edges { node { id billingAddress { city country countryCodeV2 province provinceCode zip } completedAt createdAt customer { id firstName email } updatedAt totalPriceSet { shopMoney { amount currencyCode } } } } } }
Response: ValueKind = Object : â{âdataâ:{âabandonedCheckoutsâ:{âedgesâ:[]}},âextensionsâ:{âcostâ:{ârequestedQueryCostâ:14,âactualQueryCostâ:2,âthrottleStatusâ:{âmaximumAvailableâ:2000.0,âcurrentlyAvailableâ:1998,ârestoreRateâ:100.0}},âsearchâ:[{âpathâ:[âabandonedCheckoutsâ],âqueryâ:âupdated_at:>=01/05/2025 00:00:00 AND updated_at:<31/05/2025 00:00:00â,âparsedâ:{âandâ:[{âfieldâ:âupdated_atâ,ârange_gteâ:â2025-05-01T00:00:00+00:00â,ârange_ltâ:â2025-05-31T00:00:00+00:00â},{âfieldâ:âdefaultâ,âmatch_allâ:â00â},{âfieldâ:â00â,âmatch_allâ:â00â}]},âwarningsâ:[{âfieldâ:â00â,âmessageâ:âInvalid search field for this query.â}]}]}}â
NOTE: AbandonedCheckouts docs shows updated_at as a valid query field.
I canât see any difference in the documentation between AbandonedCheckouts and Customers and Iâm using the same query syntaxand filter fields but getting different results.
Iâm also finding that using updated_at filter on Customers doesnât seem to return the correct records in both the bulk query and the normal query.
customers(query:"updated_at:>=" + fromDT + " AND updated_at:<" + toDT + "") { edges { node { id amountSpent { amount currencyCode } createdAt defaultAddress { city country countryCodeV2 province provinceCode zip } email firstName lastOrder { id createdAt } numberOfOrders updatedAt verifiedEmail } } }
in the bulk operation returns 3 records.
query CustomerList { customers(last: 200) { edges { node { id amountSpent { amount currencyCode } createdAt defaultAddress { city country countryCodeV2 province provinceCode zip } email firstName lastOrder { id createdAt } numberOfOrders updatedAt verifiedEmail } } } }
not in the bulk operation returns the same 3 records.
However, I ran a bulk operation whcih downloaded ALL Customers for the shop and looking at the records I can see 413 records in the JSONL file that have an updatedAt property in May 2025 which is the range in the query with fromDT=01/05/2025 and toDT=30/05/2025.
Most of these 413 customers were CreatedAt in that time too which is why I know the 3 is wrong because that would mean only 3 new/updated customer in the whole month which is just wrong.
I canât use CreatedAt because I need to make sure we have the updated data for each record which is why we are using UpdatedAt.
I figured out it is to do with the DateTime string format when converting from DateTime implicitly. Excplictly setting the format works as expected e.g.
string fromDT = new DateTime(2025, 5, 1).ToString(âyyyy-MM-ddT00:00:00Zâ);
string fromDT = new DateTime(2025, 5, 1).ToString(âyyyy-MM-ddâ);
Such a duh moment for me⌠Should have been more obvious to me considering Iâm passing strings to the GrahQL service that the exact format needs to be specified rather than expecting C# to use the correct format when implicitly converting to a string.
Hopefully this helps someone else getting stuck on the same thing.
I can see now in this error message that it is parsing the date properly but then using the additional 00 after the : chars as query fields: ValueKind = Object : â{âdataâ:{âabandonedCheckoutsâ:{âedgesâ:[]}},âextensionsâ:{âcostâ:{ârequestedQueryCostâ:14,âactualQueryCostâ:2,âthrottleStatusâ:{âmaximumAvailableâ:2000.0,âcurrentlyAvailableâ:1998,ârestoreRateâ:100.0}},âsearchâ:[{âpathâ:[âabandonedCheckoutsâ],âqueryâ:âupdated_at:>=01/05/2025 00:00:00 AND updated_at:<31/05/2025 00:00:00â,âparsedâ:{âandâ:[{âfieldâ:âupdated_atâ,ârange_gteâ:â2025-05-01T00:00:00+00:00â,ârange_ltâ:â2025-05-31T00:00:00+00:00â},{âfieldâ:âdefaultâ,âmatch_allâ:â00â},{âfieldâ:â00â,âmatch_allâ:â00â}]},âwarningsâ:[{âfieldâ:â00â,âmessageâ:âInvalid search field for this query.â}]}]}}â
So it is the T missing from the implicit conversion, instead having a whitespace between Date and Time components which indicates to the GraphQL that the next characters are new fields in the query.
Hi, I am new to this forum but I am having a head ache with dates, for created_at I am issuing my requests through a https POST request and the request seems to be sent correctly each time and my start date is working, but it ignores the end date totally and just keeps going?
{âqueryâ: "query {orders(first: 250, query: "created_at:>=2025-06-01T00:00:00Z created_at:<=2025-06-01T23:59:99Z") { edges { cursor node { legacyResourceId id name confirmationNumber clientIp createdAt updatedAt sourceName currencyCode email phone customerJourneySummary { customerOrderIndex daysToConversion firstVisit { landingPage referrerUrl referralCode } lastVisit { landingPage referrerUrl referralCode } } customerAcceptsMarketing displayFinancialStatus paymentGatewayNames cancelledAt cancelReason note retailLocation { id name } customAttributes { key value } discountCodes customerAcceptsMarketing currentTotalPriceSet { shopMoney { amount currencyCode } } currentTotalTaxSet { shopMoney { amount currencyCode } } currentShippingPriceSet { shopMoney { amount currencyCode } } totalDiscountsSet { shopMoney { amount currencyCode } } refunds(first: 100) { id createdAt note totalRefundedSet { shopMoney { amount currencyCode } } refundLineItems(first: 100){ edges { node { lineItem { title sku vendor id product { id } variantTitle variant { id price } quantity originalUnitPriceSet { shopMoney { amount currencyCode } } } } } } } displayAddress { firstName lastName company address1 address2 city province provinceCode country zip countryCodeV2 id phone name } billingAddress { firstName lastName company address1 address2 city province provinceCode country zip countryCodeV2 id phone name } billingAddressMatchesShippingAddress shippingAddress { firstName lastName company address1 address2 city province provinceCode country zip countryCodeV2 id phone name } lineItems(first: 100) { edges { node { title sku vendor id product { id } variantTitle quantity originalUnitPriceSet { shopMoney { amount currencyCode } } refundableQuantity variant { id price } } } } fulfillments { status trackingInfo { number company url } } tags note customerAcceptsMarketing customer { id firstName lastName email dataSaleOptOut } } } pageInfo { hasNextPage startCursor endCursor } } } "}
Itâs really annoying as I just dont seem to be able to move forward?
Thanks
Lucy
Hi Troy,
No not using dotnet - being sent as requests from Delphi as a REST request.
Iâve tried the diags option and the results now come back clean, so I will find a coded fix to sort for now I feel.
Thanks for sharing those details, it was really helpful. I did some further tests and if you keep your query field in your request along with the after:cursor then it will stop when thereâs no more in range.
Whatâs happening when you donât include the query, itâs looking for all of the orders after the End cursor, so it will keep going until there are no more orders. The query fields arenât encoded in the the cursor so youâll need to keep those in your paginated query.
Amazing!
That actually makes sense now you explain it And it works perfectly!
Can I suggest that maybe the online documentation on pagination is updated to reflect this?
I have one other question, when I was using the previous version and not shopifyql one of the fields available was order_number which was a different number to the field in orders called orders.name , it doesnât appear I can access this field anymore?
And also was available as a pure number without any prefix?
Itâs not essential but was just checking I am not missing anything?