Build1 publisher2 min readPublished
Shopify's public products.json stops paging at 25,000 products in live-store tests
Shopify's public /products.json returns HTTP 400 once page times limit passes 25,000, according to live-store tests posted on dev.to. Exports of bigger catalogs have to split the crawl by collection to reach the rest.
The Engineer · Build desk

What happened
- Asking for 500 products per page returns 250 with HTTP 200, so a loop that treats a short page as the last page stops after page one.
- On a very large store the last accepted request was page 100 at limit=250 and page 250 at limit=100, because the cap counts offset.
- The storefront endpoint pages by number alone, and an empty products array is the only signal that a crawl has reached the end.
- A test export of Allbirds walked three pages, stopped on an empty fourth, and wrote 692 products as 7,454 variant rows.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- exposure A monitoring job that catches the 400 and keeps what it collected will describe a large store as a 25,000-product store, and its output will look complete.
- exposure Price comparisons that mix products.json dollars with .js cents without converting come out wrong by a factor of 100.
- cost Full coverage of a large store is slow: a capped walk at 250 per page spends at least 100 seconds in pauses, and the collection split adds a paced walk for every collection.
The guide's export script checks the cap before the store has to refuse anything. Its loop condition is `while page * LIMIT <= OFFSET_CAP:`, with `LIMIT = 250` and `OFFSET_CAP = 25_000` [13]. I would put the check in the same place. At 250 per page the loop sends at most 100 requests and never sends the one the store would reject [1]. When the guard trips, a while-else prints a warning to split the catalog by collection [13]. Closed stores get a clear exit too: Gymshark answered 403 from behind a CDN, and the script stops with a message instead of writing an empty file [9].
According to the guide, whose author tested each behaviour on live stores on 2026-09-29 [1], the cap at least announces itself. The 400 comes back with the body `{"errors":"Page * Limit exceeds the 25000 limit."}` [3]. Leaving `limit` off is quieter. The store simply returns 30 products a page [2]. Forum answers that call `page` deprecated in favour of `page_info` are right about a different API, Shopify's authenticated Admin API [4].
The guide calls the three Allbirds pages full, but three pages of 250 hold 750 products and the export found 692, so at least one page came back short [2]. The script kept going because it breaks only on an empty batch [15].
Page size does not move the ceiling. At 100 per page, reaching the same 25,000 offset takes 250 requests instead of 100, 2.5 times as many [3]. The guide measured the cap at limits of 250 and 100 [11]. Under its stated rule, a limit that does not divide 25,000 strands the last few offsets. At the default of 30, page 833 ends at offset 24,990 and page 834 would pass the cap, leaving 10 products out of reach [4].
Price monitors have a second problem in the payload. Price and stock live on variants [6], so Allbirds' 692 products became 7,454 rows, about 10.8 per product [5]. On products.json a price is a dollar string such as "25.00". The per-product .js endpoint returns 2500, an integer in cents [7].
The workaround past the cap lists collections at `/collections.json?limit=250` and pages each `/collections/{handle}/products.json` on its own [12]. It recovers the whole catalog only if every product sits in at least one listed collection and no single collection passes 25,000 by itself [7].
What to watch
- Shopify documenting the 25,000 offset cap, or moving the storefront endpoint to cursors like the Admin API.
- Tests of whether a single collection with more than 25,000 products hits the same 400 on its own path.
- More stores blocking /products.json at the CDN, as Gymshark did, would shrink what monitoring jobs can reach at all.