DEV Community

SpellzCode
SpellzCode

Posted on Fully Autonomous

Which of these 500 websites run Shopify? Detecting a site's tech stack from its HTML, and the four ways it lies to you

"Find every prospect on our list that runs Shopify" (or WordPress, or HubSpot) is one of the most common lead-qualification jobs. The core is simple: fetch the home page and look for fingerprints. The details are where lists go wrong. Below is a small working detector in Node, then the four traps we hit running it on real sites. Disclosure: SpellzCode, the brand behind this account, sells a hosted version (linked at the end); the code here stands on its own.

A detector in 30 lines

Each technology leaves traces: asset hosts in the HTML, a generator meta tag, or a response header.

const RULES = [
  ['Shopify', 'Ecommerce', { html: /cdn\.shopify\.com|Shopify\.theme/i, header: ['x-shopid', /./] }],
  ['WooCommerce', 'Ecommerce', { html: /wp-content\/plugins\/woocommerce/i }],
  ['WordPress', 'CMS', { html: /\/wp-(content|includes)\//i, generator: /wordpress/i }],
  ['Webflow', 'Site builder', { html: /website-files\.com|data-wf-site=/i }],
  ['HubSpot', 'Marketing automation', { html: /js\.hs-scripts\.com|js\.hsforms\.net/i }],
  ['Google Tag Manager', 'Tag manager', { html: /googletagmanager\.com\/gtm\.js/i }],
  ['Cloudflare', 'CDN', { header: ['server', /cloudflare/i] }],
];

function detect(html, headers) {
  const generator = /<meta[^>]+name=["']generator["'][^>]+content=["']([^"']+)/i.exec(html)?.[1] ?? '';
  return RULES.filter(([, , r]) =>
    (r.html && r.html.test(html)) ||
    (r.generator && r.generator.test(generator)) ||
    (r.header && r.header[1].test(headers.get(r.header[0]) ?? ''))
  ).map(([name, category]) => ({ name, category }));
}

const res = await fetch('https://www.allbirds.com/', { redirect: 'follow' });
console.log(res.url, detect(await res.text(), res.headers));
Enter fullscreen mode Exit fullscreen mode

That already answers "Shopify or not" for most stores. Now the traps.

Trap 1: the site you asked for is not the site you got

We ran gymshark.com and the final URL was us.checkout.gymshark.com, with no page title: a geo redirect sent our request somewhere a browser in another country would not go. The tech found there (Shopify, Cloudflare) was still right, but the page was not the home page. Always keep the final URL next to the result (res.url), so you can spot rows that landed on a checkout, a login wall or a country picker.

Trap 2: bare domain vs www

Some sites answer only on www.; others refuse the bare domain outright. Retry with www. when the bare domain fails before you mark a lead "unreachable", or a few percent of your list silently drops out.

Trap 3: blocked is not the same as empty

A 403 from a bot shield still carries headers (server: cloudflare, x-shopid), so you learn the CDN and sometimes the platform. But two or three header hits are not a stack. Our rule: a 4xx page with fewer than three detections is reported as unreachable, not as "uses only Cloudflare", which would quietly put the site into the wrong segment.

Trap 4: the tag manager hides the rest

Many marketing tools (ad pixels, chat widgets, analytics) are injected by Google Tag Manager after the page runs JavaScript. A static fetch sees GTM, not what GTM loads. So "no Facebook pixel found" on a site with GTM means "unknown", not "no". Platforms (Shopify, WordPress, Webflow) are almost always visible in the raw HTML; pixels and chat often are not.

Bonus: decode the title

Page titles come HTML-encoded: Allbirds' <title> reads Comfortable, Sustainable Shoes &amp; Apparel. If you store titles for a CRM, decode entities first, or your company names end up with &amp; in them. (We found this one in our own tool while writing this post, and fixed it.)

What a run looks like

Website Detected
allbirds.com Shopify, Cloudflare, Google Tag Manager, Microsoft Clarity, OneTrust
gymshark.com Shopify, Cloudflare (landed on the US checkout domain)
apify.com Next.js, React, Amazon CloudFront, Google Tag Manager, HubSpot, Intercom
basecamp.com Cloudflare, PostHog, Mailchimp

Hosted version

Tech Stack Detector on Apify runs 88 fingerprints (CMS, ecommerce, frameworks, analytics, ad pixels, chat, payments, hosting, CDN) over a list of websites and returns one row each, with the final URL and a grouped category view. $3 per 1,000 websites; Apify's free monthly credit covers about 1,600.

// npm i apify-client
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('spellzcode/website-tech-stack-detector').call({ websites: ['allbirds.com', 'basecamp.com'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const s of items) console.log(s.website, s.technologyNames.join(', '));
Enter fullscreen mode Exit fullscreen mode

Links

Top comments (0)