Storefront API returns empty collection.products.filters when using fetch in Node.js, but works correctly with curl

Hi everyone,

I’m experiencing a strange issue with the Shopify Storefront API (version 2025-04).

When I query a collection’s products with filters using fetch in Node.js, the filters array is always empty ([]). However, the exact same query returns the correct filter data (with id, label, type, and values) when I run it using curl.

Code that fails (Node.js):

const response = await fetch(
  "https://my-store.myshopify.com/api/2025-04/graphql.json",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Shopify-Storefront-Access-Token": "my-shopify-storefront-access-token",
    },
    body: JSON.stringify({
      query: `
        {
          collection(handle: "keyboards") {
            products(first: 5) {
              filters {
                id
                label
                type
                values {
                  id
                  label
                  count
                }
              }
              edges {
                node {
                  title
                }
              }
            }
          }
        }
      `,
    }),
  }
);

const data = await response.json();
console.log(JSON.stringify(data, null, 2));

Result: data.data.collection.products.filters[] (empty array)

Same query that works with curl:

curl -X POST \
  https://nuphy-store.myshopify.com/api/2025-04/graphql.json \
  -H "Content-Type: application/json" \
  -H "X-Shopify-Storefront-Access-Token: my-shopify-storefront-access-token" \
  -d '{
    "query": "{ collection(handle: \"keyboards\") { products(first: 5) { filters { id label type values { id label count } } edges { node { title } } } } }"
  }'

With curl, the filters array returns properly with all the expected filter values.

What I’ve checked:

  • The Storefront Access Token has the correct permissions (including unauthenticated_read_product_listings if needed).
  • The query is identical in both cases.
  • No errors are returned in the response (status 200).
  • The rest of the data (products edges) returns correctly in Node.js.

Has anyone encountered this before? Is there something special about how Node.js fetch handles the request body or headers that could cause filters to be stripped or not computed?

Any help or insights would be greatly appreciated!

Thanks!


Note: This only happens with collection.products.filters. Other fields work normally.

2 Likes

Having the same problem.

Just yesterday the collection query was returning the filters, now it simply returns an empty array.

I am using Storefront graphql version `2026-04`.

Also it does not work in one particular store, in others it does work.

Hi there - This matches a known platform-side issue, not a problem with your Node.js code or the query itself.

We’ve seen reports of filter data intermittently coming back empty for certain stores and request patterns. Server-side requests from Node.js can be affected differently than curl due to how the platform evaluates incoming traffic, which would explain the inconsistency you’re seeing.

@Dmitri_Pavlutin, your report of it stopping suddenly and only affecting one store is consistent with the same issue. The behavior depends on per-store configuration and traffic routing, so it can appear and disappear without any changes on your end.

To confirm for sure, I’d need an x-request-id from the response headers of a failing request.

If you can share an x-request-id from a request where filters came back empty, I can trace it on our end and confirm the cause.

This same issue was reported here affecting Liquid as well, and we’re tracking it internally.

@Donal-Shopify

Hi, thanks for looking into the issue.

For example this request returned empty array of filters: returned a list of filters:

5806db13-0511-439c-94f0-b8e2f11a6446-1775560436

Thanks @Dmitri_Pavlutin . I traced that request and it’s a different situation from the known issue I mentioned earlier. Your request was correctly classified and processed normally, including the filter query against the search index for your store. So the cause is likely specific to your store’s data rather than a platform-wide issue.

A few things worth checking:

  1. Confirm the Search and Discovery app is installed and has filters enabled. Filter availability through the Storefront API depends on that app’s configuration, even for headless queries.

  2. Try making a small edit to a product in the affected collection (toggle a tag, save) to trigger a re-index. If filters were working yesterday and stopped without changes on your end, a stale search index is the most likely explanation.

  3. If filters still come back empty after that, reach out to Shopify Support (“Chat with a human”) and reference this thread. The support team can run a full search document re-import on the store, which isn’t something you can trigger yourself.

@Liaoyi, the curl vs fetch discrepancy you reported could still be the known issue I described. If you can share an x-request-id from a failing Node.js fetch request, I can check yours separately.

@Donal-Shopify

Thanks for checking into it. It was working correctly yesterday, only started to return empty list from today. Also the exactly same query sometimes returns a list of filters, and the next request returns an empty list.

I haven’t changed anything regarding the filters configuration, and Search and Discovery has always been installed.

Update: one of my colleagues on a different store also reported the issue of empty filters.

Update 2: I’ve tried changing a product in the collection to trigger filter re-index - nothing changed still empty list returned.

@Donal-Shopify

Ok, so I have checked a bit deeper. I can confirm that

5806db13-0511-439c-94f0-b8e2f11a6446-1775560436

request was made using Storefront Graphql directly in the context of a Shopify web page and actually returns the filters. This matches your feedback.

However, I am also doing a storefront query in the admin (a node.js server using react router) using unauthenticated storefront Graphql API to fetch a list of filters from the collection for the configuration purposes: and in this case the list is empty. (unfortunately I see in the response solely the content-type header - probably the graphql client strips other response headers).

Hi @Donal-Shopify

I’m using Node.js fetch() to call the Storefront API, so the request isn’t visible in the browser.

Below are the complete response.headers from the request that returned empty filters:

access-control-allow-origin *
alt-svc h3=":443"; ma=86400
cf-cache-status DYNAMIC
cf-ray 9e8970c3f894fd4e-NRT
connection keep-alive
content-encoding gzip
content-language en
content-security-policy block-all-mixed-content; frame-ancestors 'none'; upgrade-insecure-requests;
content-type application/json; charset=utf-8
date Tue, 07 Apr 2026 13:39:48 GMT
nel {"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}
powered-by Shopify
report-to {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=F4ja93u0IFss9kUaZ7MjfyzY9rgjBWP5Yq3Z%2FfQjCd4ojKgEDY9HbdQRfuUf%2BI%2BvTEL3NNGXchXJQJevt11U1LthE%2B%2BaKpl2IO8VWxQaR%2F8KbmP6CdPfRHpUh7IMoN5blRhwFBsZGGvx2e4%3D"}],"group":"cf-nel","max_age":604800}
server cloudflare
server-timing processing;dur=50;desc="gc:2", db;dur=32, asn;desc="61112", edge;desc="NRT", country;desc="JP", servedBy;desc="bv7k", graphql;desc="storefront/query/anonymous", gqlSelectionNames;desc="sfr/collection", requestID;desc="5972657a-32e3-4dba-a2dd-53555f78f345-1775569188", _y;desc="b12686a4-200b-41ef-b9a4-0a2e4eaca474", _s;desc="513cccd7-1f6d-4b62-a532-c06d2d2f94ce", cfRequestDuration;dur=153.999805
set-cookie _shopify_essential=:XXXXXX:; Max-Age=31536000; Path=/; HttpOnly; Secure; Priority=High; SameSite=Lax
shopify-complexity-score 0
transfer-encoding chunked
vary Accept,accept-encoding
x-content-type-options nosniff
x-dc gcp-asia-southeast1,gcp-asia-southeast1,gcp-asia-southeast1
x-download-options noopen
x-frame-options DENY
x-permitted-cross-domain-policies none
x-request-id 5972657a-32e3-4dba-a2dd-53555f78f345-1775569188
x-shopify-api-version 2025-04
x-shopify-api-version-warning https://shopify.dev/concepts/about-apis/versioning
x-xss-protection 1; mode=block

I also want to add that two hours ago I was able to get collection.products.filters via curl, but now it doesn’t work at all."

Hi @Donal-Shopify

I’ve discovered an issue :scream:

I tried using two React SSR frameworks and deployed them to different platforms:

  1. Using Tanstack Start, fetching data with createServerFn(), and deploying to Netlify;
  2. Using Next.js App Router, fetching data in React Server Components, and deploying to Vercel;

With the same GraphQL query, I can’t find x-request-id in the headers using either approach. In both cases, the data fetching happens on the server side. After deployment, both can successfully return the collection.products.filters data.

However, in the local environment, the returned collection.products.filters is always an empty array []!

1 Like

@Donal-Shopify

Hi everyone,

I confirm the behavior as @Liaoyi described. The filters are empty when making a request in node.js in a local dev environment, but it works well in a product environment (in my case on cloudflare).

When making a request in the context of a Shopify web store page, both local dev and production return correctly the list of filters.

Thanks.

1 Like

I can confirm this issue. In our next JS application, using the storefront API client on the server, filters are returning an empty array in localhost. The filters appear correctly in the vercel hosted production and preview environments.

I can also confirm this is a new problem as of the last 48 hours of this post.

Apologies for the delayed follow-up here. The localhost vs production pattern you all identified is consistent with the platform-side issue I mentioned earlier, and it’s useful confirmation.

The platform evaluates incoming Storefront API requests and can treat certain traffic patterns differently, which affects whether filter aggregation is computed. Requests originating from local development environments (residential IPs, generic or missing User-Agent headers) can be classified differently than requests coming through cloud providers like Vercel, Netlify, or Cloudflare. That explains why the same query works in production but returns empty filters from localhost.

The classification change that caused the April 7 recurrence was identified and rolled back the same day. The team is working on a more targeted approach that avoids false positives for legitimate headless storefront requests going forward.

I’d recommend setting a descriptive User-Agent header on your server-side Storefront API requests (something like MyApp/1.0 (+https://mysite.com)). This isn’t currently documented as a requirement, but it helps the platform distinguish your requests from automated crawlers, and it will matter more as traffic classification evolves.