← Guides

How-to

How to run Shopify App & Theme Detector — Store Audit & Gap Finder

Step-by-step: the exact input, what each field does, and the real rows that come back from public storefront.

You have a list of Shopify domains and a question about each one: what is this store actually running? Which reviews app, which email tool, which theme, how big is the catalog, are they discounting. If you sell a Shopify app, that question decides who is worth an email. If you run an agency, it decides what you pitch. The manual answer is to open each store, hit View Source, and read the script tags — which works, and is genuinely fine for three stores. It stops being fine at thirty, and it produces nothing you can sort, filter, or hand to a CRM.

This actor does that read for you across a whole list and returns it as rows.

What you’ll need

Nothing but an Apify account. The actor talks to public storefront directly.

No API key, no merchant permission, no app install, no CAPTCHA-solving credentials. Everything it reads is what a visitor with the browser devtools open can already see: the storefront HTML, the store’s own /meta.json and /products.json, and its published policy pages. That’s why it’s cheap and why it rarely breaks. The proxy toggle exists but is off by default — turn it on only if a specific store blocks the run.

Step 1 — Open the actor

Open Shopify App & Theme Detector — Store Audit & Gap Finder on the Apify Store and press Try for free. The actor’s own page on this site lists its live health and pricing.

The Shopify App & Theme Detector — Store Audit & Gap Finder listing on the Apify Store
The listing on the Apify Store. Pricing and the nightly health record are both public.

Step 2 — Fill in the input

The input form for Shopify App & Theme Detector — Store Audit & Gap Finder in the Apify Console
The input form, as it appears in the Apify Console.
FieldTypeRequiredWhat it does
domainsarrayyesShopify store domains or URLs to audit, e.g. ‘allbirds.com’. Paste a list to bulk-audit a prospect list. We audit the domains you provide (we don’t discover stores).
targetGapsarraynoApp categories that should be MISSING for a store to count as a lead — sets ‘matches_criteria’. E.g. a reviews-app vendor sets [‘reviews’]. Categories: reviews, email, sms, popups,…
includeContactsbooleannoWhen on (default), also return the store’s contact email, phone and social links from its own contact and policy pages. Each store takes a few seconds longer; turn it off for a fast tech-stack-only audit. (default True)
includeProductsbooleannoWhen off (default), return store signals only (fast, cheap — the lead-gen need). Turn on to also include the (slimmed) product catalog.
compareToPreviousbooleannoWhen on (default), diff each store against the previous run and report what changed — apps added/removed and product-count delta. Run on a schedule to track stores over time. (default True)
maxProductsPerStoreintegernoCap on products sampled per store (bounds cost). Product count caps here for very large stores. (default 1000)

Two fields decide what you actually get. domains is the one people misread: this actor audits the domains you give it. It does not go looking for Shopify stores, and there is no “find me all stores in cosmetics” mode — you bring the list, it enriches the list. Bare hosts or full URLs both work; https://Allbirds.com/collections/mens and allbirds.com normalise to the same thing.

targetGaps is what turns an audit into a lead score. Give it the app categories that must be absent for a store to count as a prospect, and matches_criteria comes back true only when every one of them is missing. A reviews-app vendor sets ["reviews"]. Leave targetGaps empty and matches_criteria is null — you still get all the detection, just no verdict. The 14 categories are reviews, email, sms, popups, search, upsell, loyalty, subscription, chat, bnpl (buy now, pay later), returns, tracking (ad and analytics pixels), translation and accessibility.

includeContacts is on by default, and it is what makes a gap a lead you can act on: the row also carries contact_email, phones and socials, read from the contact and policy pages the store publishes itself. It costs nothing extra, but each store takes a few seconds longer, so for a long list where you only want the tech stack, turn it off.

A working input:

{
  "domains": [
    "allbirds.com",
    "colourpop.com"
  ],
  "targetGaps": [
    "loyalty"
  ],
  "includeContacts": true,
  "compareToPrevious": true
}

Step 3 — Run it

Press Start. The run log names each store as it is audited, and the rows land in the dataset when the audit finishes — about four seconds a store with contacts on (five stores took 20 seconds on 2026-10-30).

You get one row per domain you submitted. Non-Shopify domains come back with is_shopify: false; a domain that never answered comes back with is_shopify: null and a fetch_error saying why, so “not Shopify” and “couldn’t reach it” never look alike. If the list is too long for the run’s time limit, the last row is a free notice saying how many domains were not audited — and those weren’t charged. An empty detected_apps list is not a failure: it usually means the store genuinely runs none of the apps in the signature list, or loads them server-side where the storefront markup doesn’t give them away. Check is_shopify first — if that’s true and the app list is empty, the detection ran and found nothing.

Step 4 — Read the output

Each run returns a labelled table, not raw JSON:

The output fields returned by Shopify App & Theme Detector — Store Audit & Gap Finder
The output view — every field the actor returns, named and typed.

Here is a real row, from a run on 2026-10-30 with targetGaps: ["loyalty"] (trimmed — the per-category has_*_app flags and a few catalog fields are left out):

{
  "domain": "allbirds.com",
  "is_shopify": true,
  "store_name": "Allbirds",
  "city": "Beverly Hills",
  "province": "California",
  "country": "US",
  "contact_email": "help@allbirds.com",
  "emails": ["help@allbirds.com"],
  "phones": ["+1 (424) 363-8064"],
  "theme_name": "[DNAM Theme July 2026]",
  "theme_base": "allbirds-theme",
  "detected_apps": [
    {"name": "Yotpo", "category": "reviews"},
    {"name": "Redo", "category": "returns"},
    {"name": "Google Tag", "category": "tracking"},
    {"name": "Microsoft Clarity", "category": "tracking"},
    {"name": "Triple Whale", "category": "tracking"},
    {"name": "Elevar", "category": "tracking"}
  ],
  "app_categories": ["returns", "reviews", "tracking"],
  "missing_categories": ["email", "sms", "popups", "search", "upsell", "loyalty",
                         "subscription", "chat", "bnpl", "translation", "accessibility"],
  "matches_criteria": true,
  "product_count": 356,
  "collection_count": 1346,
  "currency": "USD",
  "ships_to_countries_count": 1,
  "shop_pay_installments": true,
  "first_seen": true,
  "source_url": "https://allbirds.com"
}
FieldExample valueMeaning
domainallbirds.comStore domain audited
is_shopifyTrueWhether the domain is a confirmed Shopify store
store_name / city / province / countryAllbirds, Beverly Hills, California, USWho the store is and where it is based, as the store publishes it
contact_emailhelp@allbirds.comBest email on the store’s own domain
detected_apps[{"name": "Yotpo", "category": "reviews"}, …]Installed apps detected from the storefront ({name, category})
missing_categories["email", "sms", …]App categories with NO detected app — the prospecting gaps
matches_criteriaTrueTrue when all your target-gap categories are missing (a lead)
theme_name / theme_base[DNAM Theme July 2026] / allbirds-themeThe theme as named, and the theme it is built on

Most people came for missing_categories and matches_criteria — that’s the prospecting verdict, and contact_email is who to send it to. A few things to watch. matches_criteria is null, not false, when you didn’t set targetGaps. contact_email is only ever an address on the store’s own domain; addresses on other domains (a parent company, a legal agent) stay in emails. Some big brands publish only a help-center form, and their contact fields stay empty. A headless storefront (one built outside Shopify’s themes) hides most of its app markup too, so on a row with no store_name, treat a “missing” category as unconfirmed rather than a real gap. product_count is the store’s own published count, exact; on a headless store that hides it, the count falls back to the catalog sample and is capped by maxProductsPerStore (product_count_is_floor: true). And when a store has disabled /products.json (products_json_accessible: false), the catalog signals that depend on product data — price_min, price_max, discount_rate, new_products_30d — come back null. That’s expected, not a bug.

Step 5 — Export it

Open the Dataset tab and export to CSV, JSON, or Excel — or pull the same rows from the API, which is what you want if this is going to run on a schedule.

What it costs

Pay-per-store: $0.01 per Shopify store audited, so a 1,000-store prospect list is about $10.00. Domains that turn out not to be Shopify, or that never respond, are pushed to the dataset for free — you don’t pay to find out a domain was a dead end. Contact details cost nothing extra. Apify’s own platform compute is billed separately on your plan; this actor uses plain HTTP requests rather than a browser, which keeps that side small.

Where the data comes from

This reads public storefront directly. The target is Public storefront JSON. Every night a canary runs this actor against that live source and diffs the result against a frozen fixture — what “verified” means.

There is no privileged access here and no integration. Every Shopify store serves a /products.json endpoint with its catalog in it, and serves storefront HTML with its theme name and its apps’ script tags embedded — that is the whole source. The annoyance isn’t getting the data, it’s that it arrives as an undifferentiated wall of JSON and markup: apps are identified by CDN hostnames buried in script tags, the product count is only the length of a paginated list, and roughly one store in seven turns /products.json off entirely and needs a sitemap fallback. Doing that across a list by hand is the part nobody wants to do twice.

See it used

Detect a Shopify Store’s Apps & Theme is the single-store version — one domain, what it runs. Find Shopify Stores Missing a Reviews App is the app-vendor version — a list in, the ones with a gap flagged on the way out. And Audit a Competitor’s Shopify Tech Stack uses the same row to read a rival’s stack and catalog posture.

If what you actually want is to watch a store change rather than describe it once, reach for the Shopify Price & Stock Change Tracker instead: this actor tells you what a store is, the tracker tells you what moved — price changes, stock flips, products added and pulled. The natural pairing is to audit once and then watch.

Last updated 2026-10-30