Bulk API error: Access Denied for customer_reference metafields when querying reference field

Hi everyone,

I’m running a bulkOperationRunQuery to export collections and their metafields. Some collections have metafields of type customer_reference or list.customer_reference.

Since my app doesn’t have the read_customers scope, the entire bulk operation fails with an “Access Denied” error if I include the reference or references block in my query.

I tried to filter them out using the query argument: metafields(first: 50, query: "type:NOT customer_reference")

But I realized that bulkOperationRunQuery does not support the query argument for metafields (it throws an “Invalid bulk query” error).

My question: Is there a way to conditionally skip the reference field if the metafield type is a customer reference, or how can I fetch other Metaobject references without triggering a permission error on Customers?

Current query snippet:

GraphQL

metafields(first: 50) {
  edges {
    node {
      key
      value
      type
      reference {
        ... on Metaobject {
          displayName
        }
      }
    }
  }
}

Hey @Daniel1, with metafields, access is tied to the referenced object’s type.

Your collection metafields are governed by read_products, but the moment there’s a referenced Customer, the customer scope kicks in and the whole bulk op fails with ACCESS_DENIED.

The easiest fix is to add read_customers to your app.

If you can’t, a workaround is to filter to the metafields you do have access to. The metafields connection accepts a keys argument in namespace.key format, and it works inside bulk operations:

metafields(keys: ["custom.featured_metaobject", "custom.related_collection"]) {
  edges {
    node {
      key
      value
      type
      reference {
        ... on Metaobject { displayName }
      }
    }
  }
}

Hey @Daniel1, did the above help?

We’re running into the same problem with our app, and I’d like to add some context on why the current behavior is a real blocker rather than just an inconvenience.

Our app is a metafield editor/exporter - it works with arbitrary metafields across many entity types. Per Shopify’s own guidelines, an app should only request the minimum scopes it needs by default, so requesting every possible scope (read_customers, read_content, read_metaobjects, …) “just in case” a merchants are understandably reluctant to grant access to sensitive data like customers when the app doesn’t actually need it.

A typical scenario: a merchant wants to export all metafields of their products into a readable file (with reference titles resolved, etc.). Among hundreds of metafields there happen to be a couple of customer_reference fields. With the current behavior we face a dilemma:

  • force the merchant to grant read_customers (which they often don’t want to do - and rightly so),
    or
  • ask the merchant to manually allowlist the metafields to export (the keys: workaround), which is a poor UX and breaks the “export everything in one bulk query” use case entirely.

Both options are bad. There is no way to export everything with one bulk operation while staying on minimal scopes.

Important detail: the access check fires based on the concrete type of the referenced object, not on what the query actually selects. Even if the query contains only fragments for accessible types, e.g.:

reference {
  ... on Product { id title }
}

…the mere existence of a metafield pointing at a Customer/Page/Article triggers ACCESS_DENIED - for data we never asked for and that would never be returned. In a regular (non-bulk) query this is handled gracefully: reference comes back as null with an entry in errors, and all other data is returned. That’s perfectly logical — no scope, no data. But in a bulk operation the same situation fails the entire operation with no data at all.

We’re completely fine with not receiving the referenced data we don’t have access to - that’s expected. What we’re asking for is for bulk operations to handle this the same way regular queries do: return null for inaccessible references (or skip fragments for types the app can’t access) instead of failing the whole operation. This previously worked exactly that way, and the current behavior degrades the merchant experience without any apparent benefit - the access check rejects data that isn’t being requested or returned in the first place.

Is there any chance this could be revisited, or at least an officially supported way to run a bulk export that tolerates inaccessible references?