# Shopify App Store & Theme Store Scraper - Apps and Themes (`maydit/shopify-app-store-scraper`) Actor

Scrape Shopify App Store and Theme Store listings by category: name, developer, rating, review count, pricing and the Built for Shopify badge. Category and sitemap discovery.

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

## Pricing

from $1.20 / 1,000 results

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

## Shopify App Store & Theme Store Scraper

Export Shopify App Store listings and Shopify Theme Store listings as clean, structured rows. Pick a category (or a theme collection), and every app or theme comes back with its **rating, review count, pricing line, tagline, developer and Built for Shopify badge** - ready for CSV, JSON or Excel. Use it for Shopify app market research, competitor tracking, app store SEO and category monitoring, partner prospecting, or building a dataset of the Shopify ecosystem.

Apps and themes share one schema, so `type: "app"` and `type: "theme"` rows sit in the same dataset and the same spreadsheet columns.

### What you get

One row per app or theme.

| Field | Type | Description |
| --- | --- | --- |
| `type` | string | `app` or `theme`. |
| `name` | string | Listing name as shown in the store. |
| `developer` | string | null | App developer / theme designer. Apps: only when **Also open each app's page to get the developer** is enabled (see Input), and can still be `null` on a row whose detail page was throttled. Themes: filled whenever the theme's detail page loads. |
| `developerUrl` | string | null | The developer's page on the store (`/partners/...` for apps, `/designers/...` for themes). Same rule as `developer`. |
| `handle` | string | URL slug, e.g. `judgeme`, `prestige`. Stable primary key. |
| `url` | string | Canonical listing URL, with Shopify's tracking parameters removed. |
| `tagline` | string | null | The one-line pitch under the listing name. |
| `category` | string | The category or collection slug this row was collected from. |
| `rating` | number | null | Star rating out of 5. **Apps only** - the Theme Store does not publish stars (see `positivePercent`). |
| `reviewCount` | number | null | Number of reviews. For a theme with several presets this is Shopify's "reviews for all presets" total. |
| `positivePercent` | number | null | **Themes only** - Shopify's "% positive" merchant score, 0-100. Always `null` for apps. |
| `pricingSummary` | string | null | The pricing line exactly as the store shows it: `Free`, `Free plan available`, `Free to install`, `Free trial available`, `$39/month`, or `$300 one-time` for a paid theme. |
| `priceFrom` | number | null | The number parsed out of `pricingSummary`. `0` for free / freemium, the monthly or one-time amount when the store shows one, `null` when the card only says "Free trial available" (the store does not print the price there). |
| `builtForShopify` | boolean | Whether the listing carries Shopify's **Built for Shopify** badge. Always `false` for themes - that badge is an App Store programme. |
| `iconUrl` | string | null | App icon, or the theme's preview image. |
| `scrapedAt` | string | ISO timestamp of the run. |

A `SUMMARY` record is written to the run's key-value store with the sources used, how many rows were written, how many requests were throttled, and whether the run stopped on its time budget.

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | select | `apps` | `apps` scrapes apps.shopify.com, `themes` scrapes themes.shopify.com. Anything else is treated as `apps`. |
| `categories` | string list | `["store-design-site-optimization-seo"]` | App Store category slugs, or Theme Store collection slugs in themes mode. Empty falls back to one fast sample category. Full category URLs work too. |
| `maxItems` | integer | `60` | Total rows across all selected categories. |
| `includeDetails` | boolean | `false` | Open each app's own page to read the developer name. Costs one extra request per app. Ignored in themes mode, where the detail page is always loaded. |
| `onlyNewSinceLastRun` | boolean | `false` | Return only listings this Actor has not returned before for the same mode and category set. |
| `maxRunSeconds` | integer | `240` | Wall-clock budget. The crawl stops cleanly when it runs out and keeps everything collected so far. |

**Where do category slugs come from?** The App Store's own sitemap lists all of them: <https://apps.shopify.com/sitemap_categories_en.xml>. Examples: `store-design-site-optimization-seo`, `marketing-and-conversion-marketing-email-marketing`, `orders-and-shipping-shipping-solutions-shipping`, `store-management-operations-workflow-automation`.
Third-level (leaf) categories are the paginated ones and can return hundreds of apps. A top-level or mid-level slug like `store-design` has no browse grid on Shopify's side; the Actor detects that and reads the curated category page instead, which is about 30 apps on one page.

**Theme collections:** `free-themes`, `trending-themes`, `new-themes`, `horizon-themes`, `large-inventory`, `small-inventory` (see <https://themes.shopify.com/sitemap_collection_en.xml>), or the keyword `all` for the main `/themes` page.

### Example output

An app row (`mode: apps`, `includeDetails: true`):

```json
{
  "type": "app",
  "name": "Klaviyo: Email Marketing & SMS",
  "developer": "Klaviyo",
  "developerUrl": "https://apps.shopify.com/partners/klaviyo",
  "handle": "klaviyo-email-marketing",
  "url": "https://apps.shopify.com/klaviyo-email-marketing",
  "tagline": "Email marketing, SMS, & WhatsApp that turn messages into sales",
  "category": "marketing-and-conversion-marketing-email-marketing",
  "rating": 4.7,
  "reviewCount": 3319,
  "positivePercent": null,
  "pricingSummary": "Free to install",
  "priceFrom": 0,
  "builtForShopify": false,
  "iconUrl": "https://cdn.shopify.com/app-store/listing_images/5edd9000b933a8fa88c152d1e498531f/icon/CP6B2OOv3PYCEAE=.png",
  "scrapedAt": "2026-09-24T01:02:45.424Z"
}
```

A theme row (`mode: themes`, `categories: ["trending-themes"]`):

```json
{
  "type": "theme",
  "name": "Vivid",
  "developer": "Slash Themes",
  "developerUrl": "https://themes.shopify.com/designers/slashthemes",
  "handle": "vivid",
  "url": "https://themes.shopify.com/themes/vivid/presets/vivid",
  "tagline": "Exceptional design and powerful features to elevate your brand",
  "category": "trending-themes",
  "rating": null,
  "reviewCount": 90,
  "positivePercent": 96,
  "pricingSummary": "$300 one-time",
  "priceFrom": 300,
  "builtForShopify": false,
  "iconUrl": "https://cdn.shopify.com/theme-store/w0pa1h0bb3opczjgmmkf8wizcavv.jpg?width=600",
  "scrapedAt": "2026-09-24T01:05:38.866Z"
}
```

### FAQ

**Does this scrape App Store search results?**
No. `apps.shopify.com/robots.txt` contains `Disallow: *q=*` for all user agents, so search-result URLs are off limits and this Actor never requests one - it refuses at the request layer, not just by convention. Discovery is done through category pages, theme collection pages and Shopify's published sitemaps. If you need a keyword view, pull a category and filter the rows yourself.

**Why is `developer` empty on my app rows?**
Because `includeDetails` was off. A category card contains everything except the developer name, so filling that column costs one extra request per app. apps.shopify.com throttles above roughly two requests a second, which is why it is opt-in: 60 apps take about 2 seconds without it and about 90-100 seconds with it (measured 2026-09-23: 95s for 60 apps, 61 requests, 18 of them throttled and retried). Turn it on and raise `maxRunSeconds` for large jobs. When Shopify throttles hard a few rows can still come back with `developer: null` - the Actor keeps the listing row rather than failing the run. Theme rows include the developer whenever the theme's detail page loads, because a theme card carries almost nothing and the detail page has to be read anyway.

**Why is `rating` null for themes?**
The Shopify Theme Store does not publish a 5-star score. It publishes a "% positive" merchant rating and a review count, so those land in `positivePercent` and `reviewCount`, and `rating` stays null rather than being invented from the percentage.

**How does `onlyNewSinceLastRun` work?**
The Actor remembers the handles it has already returned for that mode and category set in a named key-value store. On the next run it looks at the same window - the top `maxItems` listings of each selected category - and returns only the handles it has not returned before. A run where nothing changed finishes successfully with an empty dataset; that is the "no new apps" answer, not a failure. The first run always returns everything.

**Do you return developer emails or phone numbers?**
No. This Actor collects listing data only. It does not scrape contact details, does not crawl partner contact pages, and does not enrich rows with personal data.

**What happens if the run hits its time limit?**
It stops cleanly, keeps every row already written, logs how far it got, and records `stoppedOnTime: true` in `SUMMARY`. Runs are not lost; re-run with a bigger `maxRunSeconds` or a smaller `maxItems`.

### Data source, access and limits

- **Source:** the public HTML of `apps.shopify.com` and `themes.shopify.com` (English pages), plus Shopify's published sitemaps for the category and collection lists. No API key, no Shopify account, no login, no Partner access required.
- **robots.txt is honored.** No URL with a `q=` parameter is ever requested (App Store search), no query string is ever put on `/themes` (Theme Store filter URLs), and Shopify's `surface_*` tracking parameters are stripped from the URLs it does request. These checks are enforced in code before each request.
- **Promoted placements are excluded.** Shopify mixes up to four paid placements into a category page; they are duplicates of organic listings and are dropped, so a page yields its organic listings only.
- **Rate limits.** Shopify starts returning HTTP 429 above roughly two requests a second, so requests are paced and retried. That sets the speed: 60 listing rows in about 2 seconds (3 requests), or the same 60 rows with developer lookups in about 95 seconds (61 requests, 18 of which were throttled and retried) - a bit over one app per second once detail pages are on. Timings were measured from a residential IP; Apify's datacenter IPs may be throttled harder. Persistent throttling makes the Actor finish with the listing data rather than fail.
- **Coverage.** Leaf categories paginate at 20 organic apps per page, and this Actor reads at most 40 pages per category - about 800 apps from any single category. To go past that, or to reach the 5000-row `maxItems` ceiling, pass several categories. Parent categories have no browse grid and return the ~25-30 apps Shopify curates on that one page. The Theme Store main page and each collection are single pages of roughly 20-25 themes.
- **Structure changes.** This Actor parses HTML. If Shopify changes its markup it fails loudly with an error naming the page and the marker it looked for, instead of writing empty or garbage rows.
- **Billing.** Runs are billed per result on the Apify platform, on top of your Apify plan's usage. Nothing here is free to run.
- Not affiliated with, endorsed by, or sponsored by Shopify Inc. "Shopify", "Shopify App Store" and "Built for Shopify" are trademarks of Shopify Inc., used here only to describe the public pages this Actor reads.

# Actor input Schema

## `mode` (type: `string`):

Choose the Shopify App Store (apps.shopify.com) or the Shopify Theme Store (themes.shopify.com). Both produce the same record shape, with a 'type' field of app or theme. Leave it alone to scrape apps.

## `categories` (type: `array`):

App Store category slugs. Prefer a leaf (third-level) slug such as store-design-site-optimization-seo, marketing-and-conversion-marketing-email-marketing or orders-and-shipping-shipping-solutions-shipping: those have a paginated browse grid and can return hundreds of apps. A shorter parent slug like store-design-site-optimization also works, but Shopify publishes no browse grid for it, so it returns only the ~25-30 apps curated on that one page. The full list is at https://apps.shopify.com/sitemap\_categories\_en.xml. In themes mode these are Theme Store collection slugs - free-themes, trending-themes, new-themes, horizon-themes, large-inventory, small-inventory - or the word all for the whole /themes page. Pasting a full category URL works too. Leave empty to use a fast sample category.

## `maxItems` (type: `integer`):

Stop after this many app or theme records in total, across every category selected. Each app costs one listing page request (20 apps per page) plus one detail page request, so a larger number needs a larger maxRunSeconds.

## `includeDetails` (type: `boolean`):

App category cards carry everything except the developer name, so this adds one request per app to read it (plus developerUrl). apps.shopify.com throttles above about two requests a second, so 60 apps takes roughly 90-100 seconds instead of 2, and a few rows can still come back with a null developer if Shopify throttles hard - raise maxRunSeconds when you turn it on. Ignored in themes mode, where the detail page is always loaded because the developer, tagline and review count only exist there.

## `onlyNewSinceLastRun` (type: `boolean`):

Change detection. The actor remembers the handles it has already returned for this mode and category set, and skips them next time, so a scheduled run gives you only the new arrivals. The first run always returns everything. When nothing new has appeared the run finishes successfully with an empty dataset - that is the 'no changes' answer, not an error.

## `maxRunSeconds` (type: `integer`):

Hard wall-clock limit. The crawl stops cleanly when the budget runs out and keeps every record collected so far, so the run still succeeds. Raise it for large jobs. Listing-only runs are fast (60 app records in about 2 seconds); with developer lookups on, budget about one record per second.

## Actor input object example

```json
{
  "mode": "apps",
  "categories": [
    "store-design-site-optimization-seo"
  ],
  "maxItems": 60,
  "includeDetails": false,
  "onlyNewSinceLastRun": false,
  "maxRunSeconds": 240
}
```

# Actor output Schema

## `results` (type: `string`):

One row per Shopify app or theme listing: name, developer, rating, review count, pricing summary and Built for Shopify badge.

## `summary` (type: `string`):

Totals for the run, including how many records were requested versus returned and whether the run stopped early on its time budget.

# 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 = {
    "mode": "apps",
    "categories": [
        "store-design-site-optimization-seo"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("maydit/shopify-app-store-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 = {
    "mode": "apps",
    "categories": ["store-design-site-optimization-seo"],
}

# Run the Actor and wait for it to finish
run = client.actor("maydit/shopify-app-store-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 '{
  "mode": "apps",
  "categories": [
    "store-design-site-optimization-seo"
  ]
}' |
apify call maydit/shopify-app-store-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,maydit/shopify-app-store-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/xQBN7kmaZo2mzBD7E/builds/HaTl072hWOeJc3NLf/openapi.json
