Shopify App Store Analyzer avatar

Shopify App Store Analyzer

Pricing

from $5.00 / 1,000 results

Go to Apify Store
Shopify App Store Analyzer

Shopify App Store Analyzer

Crawl Shopify App Store categories and get per-app listings plus niche-opportunity metrics (reviews per app, top-3 concentration, share of apps under 100 reviews).

Pricing

from $5.00 / 1,000 results

Rating

0.0

(0)

Developer

GapLens

GapLens

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

15 days ago

Last modified

Share

Find the under-served niches in the Shopify App Store — with numbers, not vibes.

This Actor crawls apps.shopify.com category pages and returns two things:

  1. One row per app, per category — name, URL/handle, star rating, review count, pricing label, one-line description, its rank in the category, the category's total app count, and the page it came from.
  2. One opportunity summary per category — how much demand there is per competing app, how concentrated the reviews are at the top, and how much of the long tail is still tiny.

The summary metrics are the interesting part. A category with 400 apps where the top 3 hold 70% of all reviews is a very different business proposition from one with 90 apps where the median app has 300 reviews.


What you get

Per-app dataset item

{
"name": "Judge.me Product Reviews",
"handle": "judgeme",
"url": "https://apps.shopify.com/judgeme",
"rating": 4.9,
"reviews": 44073,
"pricing": "Free plan available",
"description": "Collect and display product reviews, photo reviews and Q&A to build trust",
"position": 1,
"positionOnPage": 1,
"page": 1,
"categoryPath": "/categories/marketing-and-conversion-social-trust-product-reviews",
"categoryUrl": "https://apps.shopify.com/categories/marketing-and-conversion-social-trust-product-reviews",
"categoryName": "Product reviews",
"categoryBreadcrumb": "Marketing and conversion > Social trust > Product reviews",
"totalApps": 428,
"keywordClusters": ["gdpr_cookie"],
"parseStrategy": "data-attributes",
"scrapedAt": "2026-08-18T12:00:00.000Z",
"detail": null
}

With Include app detail pages enabled, detail is filled in:

{
"detail": {
"handle": "judgeme",
"developer": "Judge.me",
"developerUrl": "https://apps.shopify.com/partners/judgeme",
"launchedAt": "2015-06-24",
"categoryTags": ["Product reviews", "Social trust"],
"pricingPlans": [
{ "name": "Forever Free", "price": "0 USD", "features": [] },
{ "name": "Awesome", "price": "15 USD", "features": [] }
],
"strategy": "json-ld"
}
}

Per-category summary

Written to the OUTPUT record of the default key-value store and to a named dataset (category-summaries by default):

{
"path": "/categories/marketing-and-conversion-social-trust-product-reviews",
"name": "Product reviews",
"breadcrumb": "Marketing and conversion > Social trust > Product reviews",
"total_apps": 428,
"sampled": 72,
"sampled_reviews": 85815,
"top24_reviews": 81785,
"reviews_per_app": 200.5,
"top24_avg": 3408,
"median_sampled": 135,
"share_under100": 0.42,
"top3_share": 0.66,
"free_only_share": 0.19,
"reg_kw": { "gdpr_cookie": 3, "tax_vat": 1 }
}
MetricMeaningHow to read it
total_appsApps Shopify reports in the categoryCompetition
sampledApps actually crawled (24 per page)Sample size
reviews_per_appsampled_reviews / total_appsDemand per competitor. The headline number — high means merchants are buying and there is room
top24_avgMean reviews of the 24 most-reviewed appsHow big the winners get
median_sampledMedian review countIs the middle of the market alive?
share_under100Fraction of sampled apps with < 100 reviewsSize of the dead long tail
top3_shareShare of reviews held by the top 3 appsConcentration. > 0.6 means an entrenched incumbent
free_only_shareFraction priced exactly "Free"Willingness to pay
reg_kwKeyword-cluster hit countsThematic sub-niches inside the category

The OUTPUT record also contains a cross-category keywordClusters roll-up (apps deduped by name, ranked by total reviews) and a stats block listing which parser strategy was used per category plus any DEBUG_ keys that were written.

All three destinations are declared in .actor/output_schema.json (linked from actor.json as "output"), so the Console's Output tab links straight to the app dataset, the OUTPUT record, the category-summaries dataset and any DEBUG_ snapshots. The shape of a dataset item is described in .actor/dataset_schema.json.


Use cases

App developers picking a niche. Sort categories by reviews_per_app descending, then filter to top3_share < 0.5 and share_under100 > 0.6. That combination — real demand, no entrenched winner, a long tail nobody has consolidated — is where a new app has room. Add keywordClusters to hunt for regulatory sub-niches (EU withdrawal buttons, GPSR, accessibility, e-invoicing) that spike in demand when legislation lands.

Agencies and consultancies. Build a defensible "which app should we recommend" matrix per category for client stacks: rating, review volume, pricing model and launch date in one table, refreshed on a schedule. Also useful for spotting when a client's incumbent app is losing ground.

Investors and M&A. Size a category (total_apps, sampled_reviews), measure how winner-take-all it is (top3_share), and track those numbers over time by scheduling the Actor monthly and diffing the summary dataset. Review counts are a decent public proxy for installed base growth.

Competitive monitoring. Run one category weekly and watch your own position, reviews and rating against the field.


Input

FieldTypeDefaultDescription
categoriesstring[][] → auto-discoverSlugs (store-management-security-legal), paths (/categories/store-design) or full URLs. Empty means: discover every top-level category from /categories.
maxPagesPerCategoryinteger3Result pages per category, 24 apps each. 3 pages ≈ 72 apps, enough for stable concentration metrics.
followSubcategoriesbooleantrueDescend into subcategories when a page lists them instead of apps.
maxSubcategoryDepthinteger2How many levels to descend. 2 reaches the leaf level of Shopify's taxonomy.
includeAppDetailsbooleanfalseAlso visit each app page for launch date, developer, pricing plans and category tags. Roughly 25× the requests.
keywordClustersobject[]built-in[{ "name": "gdpr_cookie", "regex": "gdpr|cookie|consent" }], matched case-insensitively against name + description.
useBrowserbooleanfalseSwitch to Playwright. Only needed if pages start arriving empty — see Limitations.
proxyConfigurationobjectApify ProxyStandard Apify proxy input.
maxConcurrencyinteger5Deliberately polite.
maxRequestRetriesinteger5Each retry rotates the session and proxy.
summaryDatasetNamestringcategory-summariesNamed dataset for the per-category rows.
saveDebugHtmlbooleantrueDump raw HTML to DEBUG_<category>_<page>.html when a page yields zero apps.
enablePpeChargingbooleanfalseAdvanced, leave off. Enables the optional custom charge events — see Pricing. Billing normally comes from Apify's platform-managed events.

Example input

{
"categories": [
"store-management-security-legal",
"marketing-and-conversion-social-trust-product-reviews"
],
"maxPagesPerCategory": 3,
"includeAppDetails": false,
"keywordClusters": [
{ "name": "gdpr_cookie", "regex": "gdpr|cookie|\\bconsent|ccpa|privacy" },
{ "name": "accessibility", "regex": "accessib|wcag|\\bada\\b|\\beaa\\b|bfsg|aoda" }
],
"proxyConfiguration": { "useApifyProxy": true }
}

Whole-store scan (slower, much broader):

{ "categories": [], "maxPagesPerCategory": 3, "maxSubcategoryDepth": 2 }

Pricing

This Actor uses pay-per-event billing with Apify's two platform-managed events:

EventWhen it firesPrice
apify-actor-startOnce per run — covers category discovery, pagination and metric computation$0.05
apify-default-dataset-itemOnce per app-in-category item pushed to the default dataset$0.005

A typical two-category run at 3 pages each pushes ~144 app records: $0.05 + 144 × $0.005 ≈ $0.77.

Both events are metered by the Apify platform itself — the Actor code never calls Actor.charge() for them, and no dollar amount is hardcoded anywhere in src/. Prices are configured in the Apify Console (Monetization tab); .actor/pay_per_event.json is a documentation-only mirror of that setup.


How the scraping works (and why it keeps working)

Shopify changes its markup. Rather than betting on one selector, the Actor runs a ladder of four independent extraction strategies per page and takes the first that produces results:

  1. json-ld<script type="application/ld+json"> ItemList of SoftwareApplication entries.
  2. embedded-json — any <script type="application/json"> / window.__STATE__ blob containing an array of app-shaped objects.
  3. data-attributes — elements carrying data-controller="app-card" and data-app-card-*-value attributes.
  4. heuristic — anchors matching https://apps.shopify.com/<handle>, with rating, review count (4.9 out of 5 stars (2,190)) and pricing mined from the surrounding card node.

The strategy that won is logged and recorded on every dataset item as parseStrategy, so you can see at a glance whether the site changed shape. If zero app cards are found on a page that looks like a listing, the raw HTML is stored in the default key-value store as DEBUG_<category-slug>_<page>.html — enough to fix selectors in minutes instead of guessing.

Total counts come from /(\d[\d,]*)\s+apps/i; pagination follows rel="next" and ?page=N links.


Reproducing the analysis offline

Export the dataset as JSON and re-run the exact same metric code locally:

npm ci
npm run analyze:local -- ./dataset.json --out ./out --top 20

This prints the ranked category table plus keyword clusters, and writes leaf_metrics.json, leaf_metrics.csv and keyword_clusters.json to ./out. It accepts an Apify dataset export ([…] or {"items": […]}).


Limitations and known risks

  • Selector drift. Shopify's App Store DOM is not a public API. The four-strategy ladder plus the DEBUG_ HTML dumps make a break fast to diagnose, but a break is always possible. Check stats.parseStrategies and stats.debugKeys in OUTPUT after each run.
  • total_apps may be capped. Shopify sometimes rounds or caps the displayed count on large categories, which shifts reviews_per_app proportionally. Compare categories, don't treat the absolute value as exact.
  • Sampling bias by design. With the default 3 pages you see the top ~72 apps by Shopify's own ranking, not a random sample. sampled_reviews, top3_share and median_sampled describe the head of the category. Raise maxPagesPerCategory for a deeper (and more expensive) view.
  • Review counts ≠ installs. They are a proxy, biased toward apps that ask for reviews aggressively and toward older apps.
  • useBrowser needs a different image. Category pages are server-rendered, so the default HTTP crawler is correct and much cheaper. Playwright mode requires building from Dockerfile.playwright (apify/actor-node-playwright-chrome:20) with playwright added to dependencies.
  • Rate limiting. Concurrency is capped at 10 and defaults to 5. Use Apify Proxy; switch to residential groups if you see 403s.
  • App detail parsing is best-effort. Launch dates and pricing plans come from several fallbacks and may be null on pages with unusual layouts.
  • description is best-effort too. Shopify does not always expose the card tagline in a data attribute. The parser reads it from the description element in the card and, if a whole page still comes back without descriptions, back-fills them from the heuristic strategy (matched on handle/URL). A card that genuinely renders no tagline keeps description: null.
  • Localization. Only the default apps.shopify.com locale is crawled; localized paths are ignored.

Development

npm ci
npm run build # tsc -> dist/
npm test # vitest: parsers, metrics, and an HTTP integration test
npm run typecheck # type-checks src + tests + scripts

Tests run entirely offline against hand-written fixtures in tests/fixtures/ that mimic each DOM shape, plus a golden dataset captured from the original Python reference implementation — the metrics port is asserted to match it field for field.