Our bulk export jobs started erroring out after the metafield query change, anyone else hit this?

Had a rough Monday a couple weeks back. We build Product Data Exporter Pro, and one of the core flows is a bulk operation query that pulls products plus their metafields for CSV export. Been stable for over a year. Then support tickets started coming in from merchants saying exports were failing partway through with no output at all.

It took us a bit to trace it back to the GraphQL Admin API change where invalid metafield queries now return hard errors instead of just coming back null. It makes sense as a change, honestly; silent nulls hide real problems. But it meant that any merchant with even one stale or mistyped metafield refernce in their query (leftover from a deleted definition, a typo’d namespace, whatever) was now getting the entire bulk operation killed instead of just that one field coming back empty.

We’d been querying metafields fairly loosely since it never used to matter. Ended up rewriting that part of the query to validate metafield definitions against the shop before building the export query, instead of assuming whatever the merchant had configured would resolve cleanly. Not a huge fix in the end, maybe 40 lines, but it took longer than it should have to find because the error came back generic and didn’t point at which metafield was the problem.

Curious if anyone else building apps that touch metafields got bit by this one. Did you handle it with pre-validation like we did, or are you catching the error and retrying with the bad field stripped out instead?

@Thalia_Apps Havent been bitten by this specific one, but pre validation is the right call and I think the reasoning matters more than the outcome here.

Catch and retry with the bad field stripped is a reasonable pattern for a synchronous query, because a retry costs you a couple of hundred milliseconds. Bulk operations arent that. On a large catalogue a run can take a long time, so a retry isnt a retry, its starting the whole export again. Do that twice on a merchant with 80k products and youve turned a config problem into an outage.

It gets worse because of the thing you already noticed. The error doesnt tell you which metafield killed it. So catch and retry cant strip the bad field, it can only strip fields and see what happens, which means bisecting across full bulk runs. Thats not a fallback, thats a very slow way to arrive at the answer pre validation gives you in one cheap query.

The thing I would add to what youve built though. Youre now validating before building the export query, which fixes the runtime failure. But the merchants config was already broken before that run started, probably for weeks, and they only find out when an export fails. So Id also validate at the point they configure which metafields to include, and again whenever they open that screen.

That moves the failure from three in the morning on a scheduled export to a moment when the merchant is sat in your UI and can actually fix it. Same forty lines, different place, much better experience.

And since you now know exactly which namespace and key is stale, tell them. Something like this metafield no longer exists on your store, remove it from this export, with a button. Your original complaint was that Shopifys error was generic, so its worth making sure yours isnt. That turns a support ticket into a thing they fix themselves in five seconds.

Worth checking whether the same looseness exists anywhere else in your codebase while its fresh. Anything that queries metafields by name and assumed a null was survivable has the same latent bug, its just waiting for a different merchant.

Two things that saved us time on this same class of problem.

First, if you are validating the merchant config against metafieldDefinitions, that only covers metafields that actually have a definition. Plenty of stores carry metafields written by an old app or straight through the API that never got one, and those still return data fine when you query them by namespace and key. So a strict definition check can end up rejecting a field that works. We check definitions first, then fall back to a tiny probe query against a single product for anything that is not in the definition list.

Second, a failed bulk operation does not always mean you lost the run. The bulk operation object has partialDataUrl sitting next to url, and on a run that errored out that gives you the rows that made it through before it died. On an 80k product catalogue that is the difference between handing the merchant most of their export with a note on it, and handing them nothing. objectCount stops at the failure point too, so it gives you a rough idea of where in the catalogue it went wrong.

Neither of those replaces pre validation, they just make the failure cheaper while you are still hunting for the cause.

Did currentBulkOperation at least come back with an errorCode for you, or was that blank as well?

Haven’t hit this one myself yet but I’ve definitely been there with api changes breaking stuff that worked for months. I recommend using the shopify dev mcp to validate graphql queries before upgrading to a newer version of the api, as it can surprise you sometimes. Logging the errors via alerts (perhaps to slack or similiar tool you might use) could be helpful

Thanks for sharing as well, definitely useful for anyone touching metafields in bulk! :slight_smile:

Pre-validation is the right default, and one thing worth adding is caching the shop’s valid namespace/key set per shop with a short TTL, so a scheduled export doesn’t pay for the definition lookup on every run and you still catch a definition someone deleted last week.

Two practical notes from doing this on the CSV export side:

  1. Don’t validate only against metafieldDefinitions. Metafields written by an older app or straight through the API often have no definition but still return data, so a strict definition check will drop columns that actually work. Treat definitions as the allow-list, then probe anything not in it against one product before deciding it’s dead.

  2. Split the config into “definitely resolvable”, “resolves but undefined”, and “stale”. Only the third group should block, and it should block at config time with the exact namespace.key named, not at export time with a generic error. Then the merchant fixes it in the UI instead of filing a ticket at 3am.

On the export file itself, it also helps to keep the column set stable across runs. If a stale metafield silently disappears from the header, downstream spreadsheets and re-imports break in a quieter way than a failed job does. Emitting the column with empty values plus a note in the report is usually safer than dropping it, since a re-import with a missing column is a very different operation from one with a blank column.

And as mentioned above, partialDataUrl is worth wiring in regardless. Handing back the rows that completed with a clear “stopped at N products, cause was X” beats an empty result while you’re still debugging.

One thing I haven’t seen mentioned yet: if this is the 2026-10 metafield filtering change, checking that the definition exists isn’t quite enough.

Shopify now also validates whether that definition is actually enabled for Admin API filtering, and whether the metafield type supports the comparison being used.

So I’d include the capability in the pre-validation:

query CheckMetafield(
  $ownerType: MetafieldOwnerType!
  $namespace: String!
  $key: String!
) {
  metafieldDefinitions(
    first: 1
    ownerType: $ownerType
    namespace: $namespace
    key: $key
  ) {
    nodes {
      namespace
      key
      type {
        name
      }
      capabilities {
        adminFilterable {
          eligible
          enabled
          status
        }
      }
    }
  }
}

That should let you distinguish “definition exists” from “this definition is actually valid for the filter I’m about to put into the bulk query”.

We sit on the other side of this - our app is StoreVault, backup, the export is the product - and we ended up with a blunter rule than pre-validation: never put merchant-configured metafield references into the bulk query at all. The query asks for every metafield on the owner, and the column selection happens on our side when we process the JSONL. A stale namespace cannot kill the run because it was never in the document. The cost is bigger result files, and that turned out to be cheap; run time is dominated by product count, not by how many metafields ride along.

Where that stops working is exactly what dragino described: the moment you need a metafield in a filter rather than in the selection, you are back to validating capabilities, and the eligible/enabled split is real.

One more thing for the monitoring side, since the original failure was silent for days: the bulk operation reports errorCode on the object, not through HTTP, and a poller that only checks status can sit on FAILED without noticing. Ours alerts on any terminal state that is not COMPLETED, plus a row-count floor against the previous run - the second check is what catches the wrong-but-successful runs.