Development ·
Shopify products.json: What It Returns and Where It Falls Short
Most Shopify stores answer /products.json with their catalog as JSON. What's in it, how to page through it, and the gaps you hit when you build a product on top of it.
/products.json is a public URL that most Shopify storefronts answer with a JSON list of their published products, including variants, prices, images and tags. Add it to a store's domain, for example example-store.com/products.json, and you get the first page of the catalog without an API key. It's the quickest way to read a store's products from code. It's also undocumented as a public API, missing several fields people expect, and more work than it looks once you depend on it for many stores.
Where the endpoint comes from
Shopify's Online Store exposes JSON views of storefront resources for themes and simple integrations. Some of them are documented as part of the Ajax API, such as /cart.js and /products/<handle>.js. /products.json and /collections/<handle>/products.json aren't part of that documented set. They work on most stores, but Shopify makes no promise about their format, limits or availability, and a store or the platform can change how they behave.
What it returns
Each product in the response includes:
- id, title, handle, body_html (the description as HTML), vendor and product_type.
- tags and the dates created_at, updated_at and published_at.
- variants, each with its own id, title, sku, price, compare_at_price, available and option values.
- images and options (such as Size and Color).
This is a trimmed, made-up example. Prices are decimal strings in the store's currency.
How paging works
The endpoint returns a limited page of products. Two query parameters control it on most stores:
- limit sets the page size. Values up to 250 are commonly accepted; above that, stores fall back to a smaller page.
- page selects the page, starting at 1. You keep requesting pages until one comes back empty.
A store with 3,000 products takes 12 requests. Because page numbers are offsets into a list that can change, products added or removed while you page can shift between pages, so a long crawl can miss or duplicate products.
What it leaves out
Several things people look for aren't in the feed:
- Inventory quantities. You get available (true or false) per variant, not how many are in stock.
- Sales. Nothing about orders, revenue or what sells best. Sort order isn't by sales either.
- Market prices. Prices come in the store's default context. Stores selling in several countries can charge different prices per market, which the plain feed doesn't show.
- Unpublished products and B2B prices. Only products published to the online store appear, at public prices.
- Collections. Membership comes from /collections/<handle>/products.json, one collection at a time.
- Store-level facts. Traffic, plan, apps, ads and the team aren't part of a product feed at all.
Why it gets harder at scale
For one store, a loop over /products.json is an afternoon's work. For hundreds of stores watched every day, the problems pile up:
- Rate limits and bot protection. Storefronts throttle repeated requests, and some sit behind firewalls that block scripts outright. Expect 429 responses, challenges and empty bodies.
- Stores that turn it off or change it. Some stores block the path or return a reduced response. Headless storefronts may not serve it at all.
- Diffing. To see what changed, you need yesterday's snapshot of every product and variant, and code that compares them and decides what counts as a change. Price and stock changes per market multiply the work.
- Rules and terms. Read each store's terms and robots.txt before collecting data from it. Whether collecting public data is allowed depends on the site's terms and the law where you operate, and nobody can give a blanket answer.
A minimal crawler, and what it skips
A basic crawl of one store fits in a few lines. It shows how simple the happy path is, and the comments mark the parts a production version would need.
The function returns the catalog as it looks right now. To turn it into a tracker you'd add a database of snapshots, a diff per product and variant, per-market requests, retries, a scheduler and alerting, and you'd maintain all of it as stores change.
When to use it and when not
Use /products.json when you need one store's catalog once, for a quick analysis, a migration or a prototype. It's free and fast, and the data comes straight from the store.
Look elsewhere when you need many stores, history, changes or data the feed doesn't contain. That's the gap ShopLens fills: a store profile with counts, best sellers and product types from one request, and monitors that check up to 100 stores a day and send each product change, such as a price change, a sale starting or a variant selling out, to a webhook. How to track competitor prices on Shopify shows the monitor approach end to end, and the docs list every topic.
Data quality in the feed
Even when the endpoint answers, the data is only as clean as the merchant made it. product_type and vendor are free text, so the same kind of product has different names in different stores. Tags often mix internal labels, filters and marketing terms. Descriptions come as HTML with inline styles, embedded videos and app widgets. Prices may be placeholders on products that are not for sale. Any analysis across stores needs a cleaning step: normalize the product types, strip the HTML, and skip products with no available variants or a price of zero. Mapping free-text types to a shared taxonomy, such as the Shopify Product Taxonomy, is what makes comparisons between stores meaningful.
Related endpoints worth knowing
- /products/<handle>.js returns one product, with prices in the currency's smallest unit (6000 for $60.00). It's documented in the Ajax API.
- /collections/all?sort_by=best-selling is an HTML page, not JSON, but it orders products by the store's own sales ranking. [How to find the best-selling products of a Shopify store](/blog/shopify-best-selling-products) explains how to use it.
- /cart.js returns the visitor's cart and is a reliable way to confirm a site runs on Shopify, as in [how to check if a website is built on Shopify](/blog/check-if-website-is-shopify).