# Shopify Products Scraper: Full Catalog Export (`openfeeds/shopify-store-products-scraper`) Actor

Export full Shopify store catalogs: products, variants, SKUs, prices, compare-at prices, availability and image URLs. Gets past the 25,000-product limit and reports coverage per store.

- **URL**: https://apify.com/openfeeds/shopify-store-products-scraper.md
- **Developed by:** [OpenFeeds](https://apify.com/openfeeds) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 1,000 products

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### What does Shopify Products Scraper do?

**Shopify Products Scraper** exports the **full public product catalog of Shopify stores** to JSON, CSV or Excel: **products, variants, SKUs, prices, compare-at prices, availability and image URLs**, one clean row per product (or per variant).

You paste Shopify store URLs. For each Shopify store, the Actor reads the public `products.json` catalog feed that every Shopify storefront serves to shoppers and shopping agents, and returns every product it can reach. A **store status** record tells you, per store, how many products the catalog has and what **coverage** you got.

Think of it as a **Shopify product export API alternative** that works on any public Shopify store, without a login, an app install, a browser or a proxy.

It does **not** download images (URLs only), read stock quantities (Shopify's public feed only says in or out of stock), collect customer data or reviews, or get around any store's protection.

### Why use Shopify Products Scraper?

- 🧱 **Gets past Shopify's 25,000-product limit.** The catalog feed stops at 25,000 products. For bigger Shopify stores the Actor also reads the feed in a second order and then collection by collection, removing duplicates. Example: for a store with about 35,000 products, the feed alone stops at 25,000; in our test the extra passes raised this to 32,792 products (93.6% coverage) in about 13 minutes.
- 📏 **Coverage per store.** Each store status compares the products found with the store's own product count, so you know whether you got the whole catalog.
- 💲 **Priced per product, never per variant row.** The flat one-row-per-variant sheet costs the same as the product layout.
- 🧾 **Honest store status.** Blocked, password-protected, headless or empty stores are reported in a separate `STORE_STATUS` record and cost nothing. The dataset holds only products.
- 🚫 **No proxy needed.** The Actor reads public storefront feeds at a polite pace, honours robots.txt and Shopify's rate limits, and never tries to get around a firewall.
- ⚙️ **Apify platform extras:** scheduling, API access, integrations (Make, Zapier, Google Sheets, Slack, webhooks), datasets in JSON/CSV/Excel/HTML, and use from AI agents through the Apify MCP server.

#### Use cases

- **Assortment and price research:** export competitors' Shopify catalogs and compare product types, vendors and price bands.
- **Audit your own catalog:** find products without SKUs, broken compare-at prices or missing images.
- **Market mapping:** how big is each Shopify store in a niche, which vendors and product types do they carry?
- **BI, repricing and feed tools:** a flat SKU and price sheet per Shopify store, refreshed on a schedule.
- **AI agents:** a stable, flat product schema that is cheap to fetch.

### What data can you get from a Shopify store?

| Field                                     | Example                                                               | Description                                                                |
| ----------------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| `storeDomain` / `storeName`               | `www.deathwishcoffee.com` / `Death Wish Coffee`                       | Shopify store host and name                                                |
| `currency`                                | `USD`                                                                 | Store currency (ISO 4217)                                                  |
| `productId` / `handle`                    | `7361969684535` / `2026-death-wish-coffee-mug`                        | Shopify product id (as text) and URL slug                                  |
| `title`                                   | `2026 Death Wish Coffee Mug`                                          | Product title                                                              |
| `vendor` / `productType`                  | `Deneen Pottery` / `Mugs`                                             | Brand label and product type set by the merchant                           |
| `tags`                                    | `["Merch", "Mug", "Sale"]`                                            | Product tags                                                               |
| `priceMin` / `priceMax`                   | `30` / `30`                                                           | Lowest and highest variant price                                           |
| `compareAtPriceMax` / `onSale`            | `38` / `true`                                                         | Highest compare-at ("was") price; any variant on sale                      |
| `available` / `availableVariantCount`     | `true` / `1`                                                          | In stock now, and how many variants are                                    |
| `variants`                                | `[{ "sku": "UMDWYM26K", "price": 30, … }]`                            | Every variant: SKU, options, price, compare-at price, availability, weight |
| `options`                                 | `[{ "name": "Size", "values": ["S", "M"] }]`                          | Option axes (Size, Color, …)                                               |
| `imageUrl` / `imageUrls`                  | `https://cdn.shopify.com/…/mug.jpg`                                   | First image and all image URLs                                             |
| `productUrl`                              | `https://www.deathwishcoffee.com/products/2026-death-wish-coffee-mug` | Product page                                                               |
| `createdAt` / `updatedAt` / `publishedAt` | `2025-10-24T11:04:05-04:00`                                           | Dates as published (ISO 8601)                                              |
| `descriptionText` / `descriptionHtml`     | `Our 2026 mug…`                                                       | Optional (Include product descriptions)                                    |
| `sourceFeed` / `sourceUrl` / `scrapedAt`  | `collections/all`                                                     | Which feed page the product came from, and when                            |

**Not included:** stock quantities (Shopify's public feed only says in or out of stock), customer data, reviews.

### How to scrape products from a Shopify store

1. Open the Actor and paste one or more **Shopify store URLs**, one per line (`www.allbirds.com`, `https://www.tentree.com`, or a collection URL such as `https://www.tentree.com/collections/backpacks`).
2. Set **Max products per store**. It is also your cost cap.
3. Choose the **output layout**: one row per product (variants nested) or one row per variant (a flat sheet for Excel).
4. Click **Start**.
5. Download the products as JSON, CSV, Excel or HTML, or read them through the API. Open the `STORE_STATUS` record (Storage → Key-value store) for each Shopify store's status and coverage.

### How much does it cost to scrape a Shopify store?

The Actor uses **pay-per-event** pricing. You pay once per **unique product** written to the dataset, never per variant row:

| Apify plan | Price per product | Per 1,000 products |
| ---------- | ----------------- | ------------------ |
| Free       | $0.0009           | $0.90              |
| Starter    | $0.0008           | $0.80              |
| Scale      | $0.0007           | $0.70              |
| Business   | $0.0006           | $0.60              |

Plus **$0.00005 per run** (the standard Actor start fee).

Examples on the Free plan:

- 3 Shopify stores × 20 products (the example input) = 60 products = **$0.054**.
- One store with 2,000 products = **$1.80**.
- A store with 30,000 products, with **Max products per store** raised to 30,000: the extra passes past Shopify's 25,000 limit usually reach about 94% of such a store, so about 28,000 products = **about $25** (about $17 on Business). At the default of 10,000 products per store, the same store costs at most **$9**.

**Not charged:** variant rows, duplicate products found by more than one pass, products that could not be read, and blocked, empty, password-protected, rate-limited or skipped stores. The run stops cleanly at your maximum cost per run. If the run is moved to another server or you resurrect an aborted run, it continues where it stopped and never charges a product twice.

### Input

See the **Input** tab for every option. The main fields:

- **Shopify store URLs** (`storeUrls`): domains or URLs, one per line. A collection URL exports only that collection; `*.myshopify.com` domains work too. Product page URLs (`…/products/…`) are skipped as invalid entries, because they would export the whole store.
- **Max products per store** (`maxProductsPerStore`, default 10,000): stops each store after this many products.
- **Output layout** (`outputLayout`): `products` or `variants`.
- **Include product descriptions** (`includeDescriptions`): adds the description as text and HTML; e-mail addresses and phone numbers are removed.
- **Get past the 25,000-product limit** (`splitLargeStores`, default on) and **Extra requests per large store** (`splitRequestBudget`, default 400) for very large stores.

Three Shopify stores, 20 products each:

```json
{ "storeUrls": ["www.allbirds.com", "www.tentree.com", "www.deathwishcoffee.com"], "maxProductsPerStore": 20 }
```

One collection only:

```json
{ "storeUrls": ["https://www.tentree.com/collections/backpacks"] }
```

A flat variant sheet (one row per variant, same price):

```json
{ "storeUrls": ["www.deathwishcoffee.com"], "outputLayout": "variants" }
```

### Output

You can download the dataset in various formats such as JSON, HTML, CSV or Excel. The **Overview** view shows the key product fields; the **Variants (flat)** view unwinds every product into one row per variant, at no extra cost.

One product (layout `products`, real output, image list shortened):

```json
{
    "storeDomain": "www.deathwishcoffee.com",
    "storeName": "Death Wish Coffee",
    "currency": "USD",
    "productId": "7361969684535",
    "handle": "2026-death-wish-coffee-mug",
    "title": "2026 Death Wish Coffee Mug",
    "vendor": "Deneen Pottery",
    "productType": "Mugs",
    "tags": ["HCM", "Merch", "Mug", "Sale"],
    "productUrl": "https://www.deathwishcoffee.com/products/2026-death-wish-coffee-mug",
    "priceMin": 30,
    "priceMax": 30,
    "compareAtPriceMax": 38,
    "onSale": true,
    "available": true,
    "variantCount": 1,
    "availableVariantCount": 1,
    "options": [{ "name": "Title", "values": ["Default Title"] }],
    "variants": [
        {
            "variantId": "41956717559863",
            "variantTitle": "Default Title",
            "sku": "UMDWYM26K",
            "option1": "Default Title",
            "option2": null,
            "option3": null,
            "price": 30,
            "compareAtPrice": 38,
            "variantAvailable": true,
            "grams": 794,
            "requiresShipping": true,
            "taxable": true,
            "position": 1,
            "variantImageUrl": null,
            "variantUrl": "https://www.deathwishcoffee.com/products/2026-death-wish-coffee-mug?variant=41956717559863"
        }
    ],
    "imageUrl": "https://cdn.shopify.com/s/files/1/0271/7209/files/DeathWishCoffee_December2025_Product_Mug_Front_Web.jpg?v=1767879839",
    "imageUrls": [
        "https://cdn.shopify.com/s/files/1/0271/7209/files/DeathWishCoffee_December2025_Product_Mug_Front_Web.jpg?v=1767879839",
        "…"
    ],
    "imageCount": 8,
    "descriptionText": null,
    "descriptionHtml": null,
    "createdAt": "2025-10-24T11:04:05-04:00",
    "updatedAt": "2026-10-13T00:32:58-04:00",
    "publishedAt": "2025-12-31T08:49:09-05:00",
    "sourceFeed": "collections/all",
    "sourceUrl": "https://www.deathwishcoffee.com/collections/all/products.json?limit=20&page=1",
    "scrapedAt": "2026-10-13T04:32:58.775Z"
}
```

One row in layout `variants` (shortened): the product fields without `variants` and `options`, plus the variant columns.

```json
{
    "storeDomain": "www.deathwishcoffee.com",
    "currency": "USD",
    "productId": "7225649561655",
    "title": "Signature Standard Tee",
    "variantId": "41473922695223",
    "variantTitle": "S",
    "sku": "UTCLBF02",
    "option1": "S",
    "price": 15,
    "compareAtPrice": 30,
    "variantAvailable": true,
    "grams": 160,
    "variantUrl": "https://www.deathwishcoffee.com/products/signature-standard-tee?variant=41473922695223",
    "vendor": "Sportiqe",
    "productType": "Apparel",
    "priceMin": 15,
    "priceMax": 15,
    "onSale": true,
    "variantCount": 6,
    "availableVariantCount": 4
}
```

One Shopify store in the `STORE_STATUS` record (shortened):

```json
{
    "input": "www.deathwishcoffee.com",
    "storeDomain": "www.deathwishcoffee.com",
    "status": "OK",
    "message": "145 products; coverage 100% of 145.",
    "storeName": "Death Wish Coffee",
    "currency": "USD",
    "catalogSize": 145,
    "catalogSizeSource": "meta",
    "productsFound": 145,
    "productsPushed": 145,
    "coveragePct": 100,
    "limitReached": false,
    "capReached": false,
    "splitUsed": false,
    "requests": 3,
    "durationMs": 1472
}
```

### Store status codes explained

| Status               | What it means                                                                                                                                    | Charged?                        |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------- |
| `OK`                 | All products read, or **Max products per store** reached (`limitReached: true`)                                                                  | per product                     |
| `EMPTY`              | The store or collection has no products in its public feed                                                                                       | no                              |
| `NOT_SHOPIFY`        | No Shopify catalog feed and no Shopify signs: probably not a Shopify store                                                                       | no                              |
| `NO_PUBLIC_FEED`     | A Shopify store whose public feed is switched off (usually a headless storefront)                                                                | no                              |
| `PASSWORD_PROTECTED` | The store is behind a password page                                                                                                              | no                              |
| `BLOCKED`            | The store's own firewall blocked the request. We skip it and never try to get around protection                                                  | no                              |
| `RATE_LIMITED`       | Shopify kept answering "too many requests" after the Actor paused and slowed down. Products already exported are kept; run the store again later | only products already delivered |
| `ROBOTS_DISALLOWED`  | The store's robots.txt disallows the feed, or asks for a crawl delay over 30 s                                                                   | no                              |
| `UNREACHABLE`        | DNS, TLS or connection failure on both the `www.` and the bare domain                                                                            | no                              |
| `ERROR`              | Server errors, timeouts or broken JSON after two retries, or a feed whose products could not be read                                             | only products already delivered |
| `DUPLICATE`          | Same Shopify store as an earlier entry (for example two domains of one store)                                                                    | no                              |
| `INVALID_INPUT`      | The entry is not a domain or URL, or it is a product page URL                                                                                    | no                              |
| `STOPPED`            | The run stopped first (maximum cost reached or run aborted)                                                                                      | only products already delivered |

A run with no products **succeeds** when every store gave a store-side answer (blocked, empty, not Shopify, and so on), and **fails** only when stores could not be read because of errors or invalid input.

### Tips

- **Max products per store doubles as a cost cap:** 1,000 means at most $0.90 per store on the Free plan.
- **Collection URLs** export a slice of a Shopify store, for example one category.
- **For a flat sheet**, use layout `variants`, or keep layout `products` and open the **Variants (flat)** view or export.
- **Large stores take minutes, not seconds.** The Actor reads each store one request at a time, 0.3 to 0.5 s apart (0.4 s on average), three stores in parallel. A page holds up to 250 products, so a 25,000-product store takes about two minutes; a 35,000-product store with all extra passes took about 13 minutes in our test.
- **Schedule it** (daily or weekly) to keep a fresh catalog snapshot; compare the datasets to spot new products and price changes.

### FAQ

#### Is it legal to scrape Shopify stores?

The Actor reads only public product and price data that Shopify stores publish for shoppers. It uses no login, gets around no protection, and checks each store's robots.txt first. You are responsible for your own contracts and for how you use the data. If you are a Shopify merchant, section 1 of Shopify's Terms of Service forbids monitoring the Services with scrapers or other automated means. Individual stores' terms may also apply. This Actor is not affiliated with Shopify.

Our Actors are ethical and do not extract any private user data, such as email addresses, gender, or location. They only extract what the user has chosen to share publicly. We therefore believe that our Actors, when used for ethical purposes by Apify users, are safe. However, you should be aware that your results could contain personal data. Personal data is protected by the GDPR in the European Union and by other regulations around the world. You should not scrape personal data unless you have a legitimate reason to do so. If you're unsure whether your reason is legitimate, consult your lawyers.

#### Which endpoint does it read?

`/collections/all/products.json`, the collection JSON feed that Shopify storefronts list in their own `/agents.md`, with `/products.json` as the fallback and second order. Store name, currency and product count come from `/meta.json`. Each store's robots.txt is checked first; Shopify's default robots.txt has no rule against these paths. The Actor never calls per-product endpoints.

#### Why was a Shopify store BLOCKED or skipped?

Some stores run their own firewall in front of Shopify, and headless storefronts switch the public feed off. The Actor skips them and reports why. It never tries to get around protection: no proxies, no CAPTCHA solving.

#### Why RATE_LIMITED?

Shopify limits how fast one client can read storefront feeds. When it answers "too many requests", the Actor pauses every store and waits: the time Shopify asks for (if it asks for more than 60 s, the store stops at once), or 30 s for Shopify's usual rate-limit page, or 10 s otherwise. Then it slows that store down and retries the request once. A store gets up to 5 such waits. If Shopify keeps refusing, the store ends `RATE_LIMITED` with the products already exported kept. Run that store again later.

#### Why did I get fewer products than the store shows?

Hidden, draft, wholesale or sales-channel-limited products are not in the public feed. Very large stores are limited by Shopify's 25,000-product feed limit; the extra passes recover most, but not always all, of the rest. `coveragePct` in the store status shows how complete each export is.

#### Does it convert currencies or give stock quantities?

No. Prices are in the store's own currency (`currency`), as the store publishes them. Shopify's public feed has no stock quantities, only whether a variant is available.

#### Does it download images?

No, it returns image URLs only.

#### Can it monitor price changes?

Schedule the Actor and compare the datasets of two runs. A built-in "changes only" option is planned.

#### Do I need a proxy?

No. There is deliberately no proxy option: the Actor reads public feeds directly and skips stores that block automated access instead of trying to evade them.

### Support

Found a bug or need a field? Open an issue in the **Issues** tab. To run the Actor from your own code, see the **API** tab, for example:

```bash
curl -X POST "https://api.apify.com/v2/acts/openfeeds~shopify-store-products-scraper/run-sync-get-dataset-items?token=YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"storeUrls":["www.deathwishcoffee.com"],"maxProductsPerStore":50}'
```

### Changelog

- **1.0 (2026-10-13):** First release: full catalog export from Shopify stores, product and variant layouts, 25,000-limit split with coverage per store, store status record, pay per product.

# Actor input Schema

## `storeUrls` (type: `array`):

Store domains or URLs, one per line, e.g. <code>www.allbirds.com</code> or <code>https://www.tentree.com</code>. A collection URL (<code>…/collections/mens</code>) exports only that collection. <code>\*.myshopify.com</code> domains work too. Product page URLs (<code>…/products/…</code>) are skipped. Up to 1,000 stores per run.

## `maxProductsPerStore` (type: `integer`):

Stop after this many products from each store. You pay per product, so this is also your cost cap. Most stores have fewer than 10,000 products.

## `outputLayout` (type: `string`):

<b>One row per product</b> keeps the variants in a nested list. <b>One row per variant</b> gives a flat sheet (SKU, options, price) ready for Excel. Same price per product either way: variant rows are never billed.

## `includeDescriptions` (type: `boolean`):

Adds the merchant's product description as plain text and HTML (e-mail addresses and phone numbers are removed). Off by default to keep exports small.

## `splitLargeStores` (type: `boolean`):

Shopify's product feed stops at 25,000 products. For bigger stores, also read the feed in a second order and then collection by collection, removing duplicates. Takes longer; the store status shows the coverage reached.

## `splitRequestBudget` (type: `integer`):

Upper limit on requests for the collection split of each store above 25,000 products (listing collections and reading their pages). Higher values can raise coverage but take longer.

## `strictStores` (type: `boolean`):

Self-check: fail the run unless every store ends with status OK and at least one product.

## Actor input object example

```json
{
  "storeUrls": [
    "www.allbirds.com",
    "https://www.tentree.com/collections/accessories"
  ],
  "maxProductsPerStore": 20,
  "outputLayout": "products",
  "includeDescriptions": false,
  "splitLargeStores": true,
  "splitRequestBudget": 400,
  "strictStores": false
}
```

# Actor output Schema

## `products` (type: `string`):

Dataset with one row per unique Shopify product (or per variant in the flat layout)

## `storeStatus` (type: `string`):

Per-store status, catalog size and coverage of this run

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "storeUrls": [
        "www.allbirds.com",
        "www.tentree.com",
        "www.deathwishcoffee.com"
    ],
    "maxProductsPerStore": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("openfeeds/shopify-store-products-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "storeUrls": [
        "www.allbirds.com",
        "www.tentree.com",
        "www.deathwishcoffee.com",
    ],
    "maxProductsPerStore": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("openfeeds/shopify-store-products-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "storeUrls": [
    "www.allbirds.com",
    "www.tentree.com",
    "www.deathwishcoffee.com"
  ],
  "maxProductsPerStore": 20
}' |
apify call openfeeds/shopify-store-products-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,openfeeds/shopify-store-products-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/qScydmncCeIFB46Dv/builds/IxvzG5pbGTpJMam9f/openapi.json
