# Tech Stack Detector - Wappalyzer & BuiltWith Alternative (`feedsmith/tech-stack-detector`) Actor

Detect the CMS, ecommerce platform, analytics, tag managers, ad pixels, CRM/marketing automation, chat, payment, CDN, hosting and JS frameworks used by any list of websites, from their HTTP headers and HTML - no browser needed.

- **URL**: https://apify.com/feedsmith/tech-stack-detector.md
- **Developed by:** [TRUONG VAN HOA](https://apify.com/feedsmith) (community)
- **Categories:** Lead generation, Developer tools, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$20.00 / 1,000 site analyzeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## Tech Stack Detector — website technology checker, Wappalyzer & BuiltWith alternative

A **website technology checker** for any list of URLs: point it at a site and get back its CMS,
ecommerce platform, analytics tools, tag managers, ad pixels, CRM/marketing-automation stack, live
chat widget, payment processors, CDN, hosting, JavaScript frameworks and server software — as
clean, flat JSON. It reads the same kind of HTTP-response, header and HTML fingerprints as
Wappalyzer and BuiltWith, using the actively-maintained, GPL-licensed
[webappanalyzer](https://github.com/enthec/webappanalyzer) fingerprint database (**7,628
technologies across 109 categories**), and is priced at a fraction of the incumbent's rate.

- **A real Wappalyzer/BuiltWith alternative, not a wrapper around one.** Own detection engine
  (pattern matching, `implies`/`excludes`/`requires` chains, confidence scoring, a small DOM-subset
  selector matcher) reading the same fingerprint format, no scraping of either competitor.
- **No browser, so it's fast and cheap.** Every site is one plain HTTP fetch plus in-memory pattern
  matching — no headless Chrome, no per-site compute spike.
- **Pay only for sites that were actually analyzed.** Unreachable domains and network errors are
  saved as `status: "error"` and are never charged.
- **$0.02 per site** — $20 per 1,000 URLs, vs. the market-leading Wappalyzer Lookup API at
  $0.10/URL ($100 per 1,000).

### What it detects

7,628 technologies in 109 categories. The biggest categories (technology count from the current
fingerprint database):

| Category | Technologies tracked |
| --- | --- |
| Ecommerce | 803 |
| Marketing automation | 548 |
| CMS | 500 |
| Analytics | 430 |
| Live chat | 378 |
| CRM | 314 |
| Advertising | 240 |
| Payment processors | 176 |
| Security | 117 |
| JavaScript frameworks | 90 |
| Web servers | 95 |
| CDN | 69 |
| Hosting | 82 |
| Tag managers | 13 |

Plus WordPress/Shopify plugin & theme/app ecosystems, page builders, personalisation, appointment
scheduling, reviews, and dozens of smaller categories — the full list ships in `data/categories.json`.

### Use cases

| Use case | How |
| --- | --- |
| **B2B lead gen** ("who uses tool X") | Run with no filters to get every site's full stack, then filter the dataset downstream (Sheets/Make/n8n) for rows whose `technologyNames` contains the tool you sell against/integrate with. Or set `technologies: ["Klaviyo"]` + `onlyMatchingSites: true` to only save (and pay for) sites that use Klaviyo at all — note a filtered run's output only lists the technologies that passed the filter, not the site's full stack, so run once unfiltered if you also need to know what CMS/ecommerce platform each lead runs on |
| **Agency prospecting** | Scan a list of local-business or industry URLs, filter `categories: ["CMS", "Ecommerce"]` to spot sites on outdated or low-end platforms worth pitching a rebuild to |
| **Competitor / market research** | Point it at every competitor's site, compare their `categorySummary` for analytics, ad pixels, CRM and marketing-automation stacks |
| **Security / asset inventory** | Scan your own domain portfolio for unmanaged CMS installs, outdated JS libraries (`version` field) or forgotten third-party scripts |
| **AI agents** | Flat JSON in, flat JSON out, works through the Apify MCP server — an agent can ask "what does this site run" without parsing HTML itself |

### Input

| Field | Example | Notes |
| --- | --- | --- |
| `urls` | `["shopify.com", "wordpress.org", "stripe.com"]` | One website per line/entry. A bare domain is treated as `https://domain/`. Duplicates (after normalization) are removed automatically. |
| `minConfidence` | `50` | 1-100. Only keep a detected technology if its summed confidence score is at least this. |
| `categories` | `["Ecommerce", "Analytics"]` | Case-insensitive. Only keep technologies in one of these categories. Combines with `technologies` (see note above). |
| `technologies` | `["Shopify", "Klaviyo"]` | Case-insensitive, exact name. Only keep these specific technologies. |
| `onlyMatchingSites` | `false` | When true, a successfully-fetched site is only saved (and charged) if at least one technology survived the `categories`/`technologies` filters. Failed fetches are always saved as `status: "error"` and never charged. |
| `includeDns` | `false` | Also resolve TXT/MX/NS/SOA/CNAME records for the handful of fingerprints only identifiable that way. Adds a DNS lookup per site. |
| `includeRobots` | `false` | Also fetch and scan `/robots.txt`. Adds one extra request per site. |
| `outputFormat` | `"site"` (default) or `"technology"` | `"site"`: one record per site with a nested `technologies` array. `"technology"`: one dataset row per (site, technology, category) — easier to filter/pivot in Excel/Sheets. Either way you're charged once per site. |
| `maxConcurrency` | `10` | 1-50. Sites fetched in parallel (at most one in-flight request per host regardless of this setting). |
| `proxyConfiguration` | `{ "useApifyProxy": false }` | Optional. Route requests through an Apify proxy if a lot of your targets block datacenter IPs. Off by default. |

#### Example 1: basic scan

```json
{
  "urls": ["shopify.com", "wordpress.org", "stripe.com"],
  "minConfidence": 50
}
```

#### Example 2: lead-gen list — sites using a specific marketing tool

```json
{
  "urls": ["allbirds.com", "gymshark.com", "shopify.com"],
  "technologies": ["Klaviyo"],
  "onlyMatchingSites": true
}
```

#### Example 3: flat spreadsheet-friendly rows, one per detected technology

```json
{
  "urls": ["allbirds.com", "gymshark.com"],
  "categories": ["Ecommerce", "Analytics", "CRM"],
  "outputFormat": "technology"
}
```

### Output

One record per site (`outputFormat: "site"`, default). Real output from a live run against
`allbirds.com`:

```json
{
  "url": "https://allbirds.com/",
  "finalUrl": "https://www.allbirds.com/",
  "domain": "www.allbirds.com",
  "status": "ok",
  "httpStatus": 200,
  "title": "Allbirds: Comfortable, Sustainable Shoes & Apparel",
  "metaDescription": "Allbirds: The world’s most comfortable shoes, flats, and clothing made with natural materials like merino wool and eucalyptus. FREE shipping & returns.",
  "language": "en-US",
  "technologies": [
    {
      "name": "Shopify",
      "slug": "shopify",
      "categories": ["Ecommerce"],
      "confidence": 100,
      "version": null,
      "website": "https://shopify.com",
      "cpe": null,
      "detectedBy": ["cookies", "meta", "scriptSrc"]
    },
    {
      "name": "Cloudflare",
      "slug": "cloudflare",
      "categories": ["CDN"],
      "confidence": 100,
      "version": null,
      "website": "https://www.cloudflare.com",
      "cpe": "cpe:2.3:a:cloudflare:cloudflare:*:*:*:*:*:*:*:*",
      "detectedBy": ["headers"]
    },
    {
      "name": "Google Tag Manager",
      "slug": "google-tag-manager",
      "categories": ["Tag managers"],
      "confidence": 100,
      "version": null,
      "website": "https://www.google.com/tagmanager",
      "cpe": null,
      "detectedBy": ["html"]
    },
    {
      "name": "Apple Pay",
      "slug": "apple-pay",
      "categories": ["Payment processors"],
      "confidence": 100,
      "version": null,
      "website": "https://www.apple.com/apple-pay",
      "cpe": null,
      "detectedBy": ["dom"]
    },
    {
      "name": "PayPal",
      "slug": "paypal",
      "categories": ["Payment processors"],
      "confidence": 100,
      "version": null,
      "website": "https://paypal.com",
      "cpe": "cpe:2.3:a:paypal:paypal:*:*:*:*:*:*:*:*",
      "detectedBy": ["dom"]
    },
    {
      "name": "Priority Hints",
      "slug": "priority-hints",
      "categories": ["Performance"],
      "confidence": 100,
      "version": null,
      "website": "https://wicg.github.io/priority-hints/",
      "cpe": null,
      "detectedBy": ["dom"]
    },
    {
      "name": "HSTS",
      "slug": "hsts",
      "categories": ["Security"],
      "confidence": 100,
      "version": null,
      "website": "https://www.rfc-editor.org/rfc/rfc6797#section-6.1",
      "cpe": null,
      "detectedBy": ["headers"]
    },
    {
      "name": "HTTP/3",
      "slug": "http-3",
      "categories": ["Miscellaneous"],
      "confidence": 100,
      "version": null,
      "website": "https://httpwg.org/",
      "cpe": null,
      "detectedBy": ["headers"]
    },
    {
      "name": "Open Graph",
      "slug": "open-graph",
      "categories": ["Miscellaneous"],
      "confidence": 100,
      "version": null,
      "website": "https://ogp.me",
      "cpe": null,
      "detectedBy": ["dom"]
    },
    {
      "name": "Swiper",
      "slug": "swiper",
      "categories": ["JavaScript libraries"],
      "confidence": 100,
      "version": null,
      "website": "https://swiperjs.com",
      "cpe": null,
      "detectedBy": ["dom"]
    }
  ],
  "technologyNames": ["Apple Pay", "Cloudflare", "Google Tag Manager", "HSTS", "HTTP/3", "Open Graph", "PayPal", "Priority Hints", "Shopify", "Swiper"],
  "categorySummary": {
    "Payment processors": ["Apple Pay", "PayPal"],
    "CDN": ["Cloudflare"],
    "Tag managers": ["Google Tag Manager"],
    "Security": ["HSTS"],
    "Miscellaneous": ["HTTP/3", "Open Graph"],
    "Performance": ["Priority Hints"],
    "Ecommerce": ["Shopify"],
    "JavaScript libraries": ["Swiper"]
  },
  "responseTimeMs": 8696,
  "error": null,
  "analyzedAt": "2026-09-18T12:40:58.254Z"
}
```

With `outputFormat: "technology"`, the same detection becomes one row per (site, technology,
category) — this first row is the real, unmodified output of that transform on the record above:

```json
{
  "url": "https://allbirds.com/",
  "finalUrl": "https://www.allbirds.com/",
  "domain": "www.allbirds.com",
  "status": "ok",
  "httpStatus": 200,
  "title": "Allbirds: Comfortable, Sustainable Shoes & Apparel",
  "responseTimeMs": 8696,
  "error": null,
  "analyzedAt": "2026-09-18T12:40:58.254Z",
  "technology": "Apple Pay",
  "category": "Payment processors",
  "confidence": 100,
  "version": null,
  "detectedBy": "dom"
}
```

Field notes:

- `detectedBy` lists which signal(s) matched: `headers`, `cookies`, `meta`, `html`, `scriptSrc`,
  `scripts` (inline script text), `url`, `css`, `dom`, and (opt-in) `robots`/`dns`.
- `confidence` is the sum of every matching pattern's own confidence, capped at 100.
- `version` is `null` when no pattern captured a version string, not a failure.
- `error` and `httpStatus` are populated (and `technologies` is empty) on `status: "error"` rows —
  unreachable domains, timeouts, DNS failures, or a target the SSRF guard refused to fetch.

### Pricing

Pay per event — **$0.02 per site**, charged once a site has been successfully fetched, analyzed,
**and saved**:

| Event | When |
| --- | --- |
| `url-analyzed` | Once per site, only when `status: "ok"` **and** the record was actually saved to the dataset |

- Failed fetches (`status: "error"`) are always saved for visibility, but never charged.
- With `onlyMatchingSites: true`, a successful site that has zero technologies surviving the
  `categories`/`technologies` filters is **not saved at all** — so it is not charged either.
- `outputFormat: "technology"` can turn one site into several dataset rows, but the charge is still
  once per site, not per row.
- Set **Max total charge** on the run to cap spend; the Actor stops cleanly (finishes in-flight
  fetches, no partial charges) when the cap is reached.

$20 per 1,000 sites vs. the Wappalyzer Lookup API's $100 per 1,000 (5x cheaper for the same class
of HTTP-based fingerprint).

### Accuracy & limits

Live-validated against 28 well-known sites with independently-known tech stacks (Shopify stores,
wordpress.org, a WooCommerce store, a Magento store, HubSpot, Intercom, Cloudflare-fronted sites,
vercel.com, a Nuxt site, a Drupal site, a Ghost blog, plus several JS-framework and government
sites) — **16 of 20 sites with an unambiguous known platform had it correctly identified**, 137
unit tests pass, and engine time is ~1.1 s/page after an indexed-DOM-matching optimization (down
from 4.0 s), with peak memory 285 MB for the run.

- **This is HTTP-based detection, not a browser.** It reads the HTML, headers and cookies of the
  first response — technologies that only appear after JavaScript executes (client-side chat
  widgets injected by a tag manager, some JS-rendered React apps, single-page apps that hydrate
  content client-side) are not detected. That's the confirmed cause of every known miss in
  validation: Intercom's own site (its chat widget is JS-injected), react.dev (its own React usage
  isn't in the initial HTML), and drupal.org (only a CDN/cache header signature was visible in the
  raw response). `ghost.org` was correctly identified as **not** running Ghost — it actually runs
  Hugo on Netlify.
- **One record per URL, home page only.** No crawling of internal pages — pass every URL you want
  analyzed.
- **Sites that block non-browser clients** (aggressive bot detection) will return few or no
  signals; the Actor uses a standard desktop Chrome user agent but does not solve CAPTCHAs or
  render JS to get past such blocks.
- **`dom` selector matching is a subset of Wappalyzer's `dom` field**: `tag`, `#id`, `.class`,
  `[attr]`, `[attr="v"]`, `[attr*="v"]`, `[attr^="v"]` and combinations of those on a single
  element, with `exists`/`attributes`/`text` checks. Multi-element combinators and JS-`properties`
  checks (which need a live DOM) are recognized and skipped, not silently guessed.
- `js` and `xhr` fingerprint fields (Wappalyzer signals that need a running browser to evaluate)
  are not supported — by design, since this Actor never launches a browser.

### Code example

A runnable Python script (only `requests` needed) that calls this Actor and writes a CSV: [tech-stack/find\_shopify\_klaviyo.py](https://github.com/ankaibua-spec/feedsmith-examples/blob/master/tech-stack/find_shopify_klaviyo.py) — find which sites in a list run Shopify **and** Klaviyo, saved to CSV.

### FAQ

**Is this affiliated with Wappalyzer or BuiltWith?** No. This is an independent tool built on the
open-source [webappanalyzer](https://github.com/enthec/webappanalyzer) fingerprint database (the
maintained fork of the technologies Wappalyzer itself was originally built from), with its own
detection engine.

**Why didn't it detect the CMS I know a site uses?** Most likely the signal only appears after
JavaScript runs — see Accuracy & limits above. Try `includeDns`/`includeRobots` if the technology
you're after has a DNS- or robots.txt-based fingerprint.

**Why is `version` empty for a technology I can see the version of on the page?** Only some
fingerprints include a version-extracting pattern; many technologies (most CDNs, analytics tools)
don't expose a version in headers/HTML at all.

**Can I run this against my own internal/staging URLs?** Only if they resolve to a public IP. A
built-in SSRF guard refuses private, loopback, link-local, unique-local and CGNAT addresses (and
re-checks on every redirect hop) so the Actor can't be pointed at internal infrastructure.

**Something wrong or missing?** Open an issue on the Actor's Issues tab.

### License & attribution

Technology fingerprints are from [enthec/webappanalyzer](https://github.com/enthec/webappanalyzer)
(GPL-3.0), the actively-maintained continuation of the original Wappalyzer fingerprint database.
This Actor's fingerprint data and detection logic are distributed under **GPL-3.0-only** — see
`LICENSE`. The complete corresponding source code is available to any user on request: open an issue
on the Actor's **Issues** tab and it will be shared. Not affiliated with, endorsed by, or sponsored by Wappalyzer or BuiltWith; both names
are used only to describe compatibility/comparison.

# Actor input Schema

## `urls` (type: `array`):

One website per line. A bare domain (e.g. shopify.com) is treated as https://shopify.com/. Duplicates (after normalization) are removed automatically.

## `minConfidence` (type: `integer`):

Only keep a detected technology if its confidence score (0-100, summed across every matching signal) is at least this. Lower this to see weaker/speculative matches, raise it to only see near-certain ones.

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

Only keep technologies in one of these categories (case-insensitive, e.g. "Ecommerce", "Analytics", "CMS"). Combines with "Filter by technology" using AND; leave empty to not filter by category. Also controls which sites get saved when "Only save sites with a match" is on.

## `technologies` (type: `array`):

Only keep these specific technologies by name (case-insensitive, e.g. "Shopify", "Klaviyo"). Combines with "Filter by category" using AND; leave empty to not filter by technology. Also controls which sites get saved when "Only save sites with a match" is on.

## `onlyMatchingSites` (type: `boolean`):

When on, a site is only saved (and charged) if at least one technology survives the category/technology filters above. Use this for lead-gen lists like "Shopify stores using Klaviyo" (set "Filter by technology" to Klaviyo and turn this on). Failed fetches are always saved as status "error" and are never charged.

## `includeDns` (type: `boolean`):

Also resolve TXT/MX/NS/SOA/CNAME records to catch technologies only identifiable that way (e.g. some CDN/registrar/hosting fingerprints). Adds a DNS lookup per site.

## `includeRobots` (type: `boolean`):

Also fetch and scan /robots.txt for the handful of technologies that leave a signature there. Adds one extra request per site.

## `outputFormat` (type: `string`):

"One row per site" nests every detected technology inside a single record per site. "One row per site x technology" flattens that into one dataset row per (site, technology, category) - easier to filter/pivot in Excel/Sheets. Either way you're only charged once per site.

## `maxConcurrency` (type: `integer`):

Maximum number of sites fetched in parallel (at most one in-flight request per host regardless of this setting, to be polite to any single site).

## `proxyConfiguration` (type: `object`):

Optional. Route requests through an Apify proxy, e.g. if a lot of your target sites block datacenter IPs. Off by default (direct connection).

## Actor input object example

```json
{
  "urls": [
    "shopify.com",
    "wordpress.org",
    "stripe.com"
  ],
  "minConfidence": 50,
  "categories": [],
  "technologies": [],
  "onlyMatchingSites": false,
  "includeDns": false,
  "includeRobots": false,
  "outputFormat": "site",
  "maxConcurrency": 10,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One record per analyzed website: detected technologies with category, confidence and version, plus page metadata and status.

# 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 = {
    "urls": [
        "shopify.com",
        "wordpress.org",
        "stripe.com"
    ],
    "categories": [],
    "technologies": [],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("feedsmith/tech-stack-detector").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 = {
    "urls": [
        "shopify.com",
        "wordpress.org",
        "stripe.com",
    ],
    "categories": [],
    "technologies": [],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("feedsmith/tech-stack-detector").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 '{
  "urls": [
    "shopify.com",
    "wordpress.org",
    "stripe.com"
  ],
  "categories": [],
  "technologies": [],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call feedsmith/tech-stack-detector --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,feedsmith/tech-stack-detector"
        }
    }
}
```

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/cUbXXIv8aL2dbWfqk/builds/7EE7BUnoXb3kT9j7Z/openapi.json
