Shopify App Store Analyzer
Pricing
from $5.00 / 1,000 results
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
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
15 days ago
Last modified
Categories
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:
- 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.
- 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 }}
| Metric | Meaning | How to read it |
|---|---|---|
total_apps | Apps Shopify reports in the category | Competition |
sampled | Apps actually crawled (24 per page) | Sample size |
reviews_per_app | sampled_reviews / total_apps | Demand per competitor. The headline number — high means merchants are buying and there is room |
top24_avg | Mean reviews of the 24 most-reviewed apps | How big the winners get |
median_sampled | Median review count | Is the middle of the market alive? |
share_under100 | Fraction of sampled apps with < 100 reviews | Size of the dead long tail |
top3_share | Share of reviews held by the top 3 apps | Concentration. > 0.6 means an entrenched incumbent |
free_only_share | Fraction priced exactly "Free" | Willingness to pay |
reg_kw | Keyword-cluster hit counts | Thematic 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
| Field | Type | Default | Description |
|---|---|---|---|
categories | string[] | [] → auto-discover | Slugs (store-management-security-legal), paths (/categories/store-design) or full URLs. Empty means: discover every top-level category from /categories. |
maxPagesPerCategory | integer | 3 | Result pages per category, 24 apps each. 3 pages ≈ 72 apps, enough for stable concentration metrics. |
followSubcategories | boolean | true | Descend into subcategories when a page lists them instead of apps. |
maxSubcategoryDepth | integer | 2 | How many levels to descend. 2 reaches the leaf level of Shopify's taxonomy. |
includeAppDetails | boolean | false | Also visit each app page for launch date, developer, pricing plans and category tags. Roughly 25× the requests. |
keywordClusters | object[] | built-in | [{ "name": "gdpr_cookie", "regex": "gdpr|cookie|consent" }], matched case-insensitively against name + description. |
useBrowser | boolean | false | Switch to Playwright. Only needed if pages start arriving empty — see Limitations. |
proxyConfiguration | object | Apify Proxy | Standard Apify proxy input. |
maxConcurrency | integer | 5 | Deliberately polite. |
maxRequestRetries | integer | 5 | Each retry rotates the session and proxy. |
summaryDatasetName | string | category-summaries | Named dataset for the per-category rows. |
saveDebugHtml | boolean | true | Dump raw HTML to DEBUG_<category>_<page>.html when a page yields zero apps. |
enablePpeCharging | boolean | false | Advanced, 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:
| Event | When it fires | Price |
|---|---|---|
apify-actor-start | Once per run — covers category discovery, pagination and metric computation | $0.05 |
apify-default-dataset-item | Once 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:
json-ld—<script type="application/ld+json">ItemListofSoftwareApplicationentries.embedded-json— any<script type="application/json">/window.__STATE__blob containing an array of app-shaped objects.data-attributes— elements carryingdata-controller="app-card"anddata-app-card-*-valueattributes.heuristic— anchors matchinghttps://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 cinpm 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. Checkstats.parseStrategiesandstats.debugKeysinOUTPUTafter each run. total_appsmay be capped. Shopify sometimes rounds or caps the displayed count on large categories, which shiftsreviews_per_appproportionally. 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_shareandmedian_sampleddescribe the head of the category. RaisemaxPagesPerCategoryfor 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.
useBrowserneeds a different image. Category pages are server-rendered, so the default HTTP crawler is correct and much cheaper. Playwright mode requires building fromDockerfile.playwright(apify/actor-node-playwright-chrome:20) withplaywrightadded 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
nullon pages with unusual layouts. descriptionis 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 keepsdescription: null.- Localization. Only the default
apps.shopify.comlocale is crawled; localized paths are ignored.
Development
npm cinpm run build # tsc -> dist/npm test # vitest: parsers, metrics, and an HTTP integration testnpm 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.