# Website Tech Stack Detector: frameworks, CMS, analytics (`thatmike1/tech-stack-detector`) Actor

Detect the technology stack behind any list of websites: CMS, ecommerce platform, JavaScript framework, analytics, CDN, web server and more, with versions, confidence and the evidence for every match.

- **URL**: https://apify.com/thatmike1/tech-stack-detector.md
- **Developed by:** [Michal Pšenčík](https://apify.com/thatmike1) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.005 / actor start

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Website Tech Stack Detector (frameworks, CMS, analytics)

### What does Website Tech Stack Detector do?

Give this Actor a list of websites and it tells you what each one is built with: the **CMS**, the
**ecommerce platform**, the **JavaScript framework**, the **analytics and tag managers**, the **CDN**, the
**web server**, the **programming language** and around a hundred other categories. Every match comes back
with its **category**, its **version** where the site gives one away, a **confidence score** and the
**evidence** — the exact header, cookie, script URL, meta tag or HTML pattern that produced it, so you can
check a result instead of trusting it.

It fetches each site over plain HTTP, downloads its script files and searches those too. There is no
browser, so a thousand domains cost you a few minutes rather than an hour.

Try it from the **Input** tab: leave everything at its default and hit Start to profile three well-known
sites.

### Why use Website Tech Stack Detector?

- **Sales prospecting.** Filter a list of domains down to the ones running Shopify, HubSpot, WordPress or a
  competitor's product, then hand your team the ones worth calling.
- **Competitive and market research.** Take any list of companies and count what they actually run, rather
  than what a vendor's case-study page claims.
- **Lead enrichment inside your own funnel.** Feed the domains from your signup form through the Actor and
  attach the stack to each record in your CRM.
- **Security and compliance sweeps.** Spot the sites in your estate still advertising an old server, an
  outdated jQuery, or a tag manager nobody remembers adding.
- **Agency audits.** Produce the technology inventory slide for a pitch from a URL list in one run.

Every technology carries the evidence that produced it, which is the part most detectors leave out. When a
result looks wrong you can see whether it came from a real fingerprint or from a Content-Security-Policy
header that merely mentions a vendor.

### How to use Website Tech Stack Detector

1. Click **Try for free** (or open the Actor in Apify Console).
2. On the **Input** tab, put your websites in **Websites to analyse**. You can paste them one per line,
   upload a CSV or text file, or link a Google Sheet. A bare domain like `example.com` works; it is read as
   `https://example.com`.
3. Leave the rest alone for a first run. If you want fewer weak matches, raise **Minimum confidence**. If a
   site hides its stack deep in its JavaScript bundles, raise **Scripts to download per site**.
4. Click **Start**. Ten sites take about ten seconds.
5. Open the **Output** tab (or **Storage → Dataset**) and export as JSON, CSV, Excel or HTML, or pull it
   through the [Apify API](https://docs.apify.com/api/v2).

For a recurring inventory, use **Schedules** in Apify Console to re-run the same list every week and diff
the results.

### Input

All fields are optional; the defaults profile three sample sites.

| Field                | Type    | Default              | Description                                                                                                                                               |
| -------------------- | ------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `startUrls`          | array   | three sample sites   | The websites to analyse. One dataset item comes back per URL. Accepts `{ "url": "..." }` objects, a pasted list, an uploaded file or a Google Sheet.      |
| `maxItems`           | integer | `1000`               | Stop after this many URLs. `0` means no limit.                                                                                                            |
| `maxScripts`         | integer | `8`                  | How many external JavaScript files to download and search per site. Inline scripts are always searched. `0` skips external scripts and makes runs faster. |
| `minConfidence`      | integer | `0`                  | Drop technologies detected below this confidence, 0 to 100.                                                                                               |
| `includeFailedUrls`  | boolean | `true`               | Write a row for a URL that could not be fetched, with the error in it. A failed row is a dataset item and is billed as one. Set to `false` to skip them.  |
| `proxyConfiguration` | object  | Apify proxy disabled | Optional. The Actor fetches the URLs you supply, so no proxy is needed for most lists.                                                                    |

Example input:

```json
{
    "startUrls": [{ "url": "https://wordpress.org" }, { "url": "https://www.shopify.com" }],
    "maxScripts": 8,
    "minConfidence": 0
}
```

### Output

Each website is one dataset item. Download the dataset in various formats such as JSON, HTML, CSV, or Excel.

The example below is a real run against `https://wordpress.org`, trimmed to five of the thirteen
technologies it found and to the first two pieces of evidence each.

```json
{
    "url": "https://wordpress.org/",
    "finalUrl": "https://wordpress.org/",
    "statusCode": 200,
    "success": true,
    "error": null,
    "technologies": [
        {
            "name": "WordPress",
            "categories": ["CMS", "Blogs"],
            "website": "https://wordpress.org",
            "version": "7.2",
            "confidence": 100,
            "evidence": [
                {
                    "type": "header",
                    "key": "link",
                    "pattern": "rel=\"https://api\\.w\\.org/\"",
                    "value": "rel=\"https://api.w.org/\""
                },
                {
                    "type": "meta",
                    "key": "generator",
                    "pattern": "^WordPress(?: ([\\d.]+))?",
                    "value": "WordPress 7.2"
                }
            ]
        },
        {
            "name": "Gutenberg",
            "categories": ["WordPress plugins", "Editors"],
            "website": "https://github.com/WordPress/gutenberg",
            "version": "23.9.0",
            "confidence": 100,
            "evidence": [
                {
                    "type": "scriptSrc",
                    "pattern": "/wp-content/plugins/gutenberg/",
                    "value": "/wp-content/plugins/gutenberg/",
                    "source": "https://wordpress.org/wp-content/plugins/gutenberg/build/modules/block-library/navigation/view.min.js?ver=1bf28ded04f9f188bdcb-20240308"
                },
                {
                    "type": "dom",
                    "key": "link[href*='/wp-content/plugins/gutenberg/'][href]",
                    "pattern": "/wp-content/plugins/gutenberg/.+\\.css(?:\\?ver=(\\d+(?:\\.\\d+)+))?",
                    "value": "/wp-content/plugins/gutenberg/build/styles/block-library/navigation/style.min.css?ver=23.9.0"
                }
            ]
        },
        {
            "name": "Nginx",
            "categories": ["Web servers", "Reverse proxies"],
            "website": "https://nginx.org/en",
            "version": null,
            "confidence": 100,
            "evidence": [
                {
                    "type": "header",
                    "key": "server",
                    "pattern": "nginx(?:/([\\d.]+))?",
                    "value": "nginx"
                }
            ]
        },
        {
            "name": "Google Tag Manager",
            "categories": ["Tag managers"],
            "website": "https://www.google.com/tagmanager",
            "version": null,
            "confidence": 100,
            "evidence": [
                {
                    "type": "html",
                    "pattern": "googletagmanager\\.com/ns\\.html[^>]+></iframe>",
                    "value": "googletagmanager.com/ns.html?id=GTM-P24PF4B\" height=\"0\" width=\"0\" style=\"display:none;visibility:hidden\"></iframe>"
                }
            ]
        },
        {
            "name": "MySQL",
            "categories": ["Databases"],
            "website": "https://mysql.com",
            "version": null,
            "confidence": 100,
            "evidence": [
                {
                    "type": "implied",
                    "pattern": "WordPress",
                    "value": "implied by WordPress"
                }
            ]
        }
    ],
    "technologyNames": [
        "Google Font API",
        "Google Tag Manager",
        "Gutenberg",
        "HSTS",
        "MySQL",
        "Nginx",
        "Open Graph",
        "PHP",
        "Priority Hints",
        "RSS",
        "WordPress",
        "WordPress Block Editor",
        "WordPress Site Editor"
    ],
    "technologyCount": 13,
    "categories": [
        "Blogs",
        "CMS",
        "Databases",
        "Editors",
        "Font scripts",
        "Miscellaneous",
        "Page builders",
        "Performance",
        "Programming languages",
        "Reverse proxies",
        "Security",
        "Tag managers",
        "Web servers",
        "WordPress plugins"
    ],
    "scriptsFetched": 5,
    "detectedAt": "2026-09-03T17:01:44.906Z"
}
```

A URL that could not be fetched comes back as a row too, so nothing disappears from your list silently:

```json
{
    "url": "https://this-domain-does-not-exist-98765.com/",
    "finalUrl": null,
    "statusCode": null,
    "success": false,
    "error": "getaddrinfo ENOTFOUND this-domain-does-not-exist-98765.com",
    "technologies": [],
    "technologyNames": [],
    "technologyCount": 0,
    "categories": [],
    "scriptsFetched": 0,
    "detectedAt": "2026-09-03T17:02:18.676Z"
}
```

#### Data fields

| Field             | Description                                                                                           |
| ----------------- | ----------------------------------------------------------------------------------------------------- |
| `url`             | The URL as you supplied it.                                                                           |
| `finalUrl`        | Where it ended up after redirects.                                                                    |
| `statusCode`      | HTTP status of the final response. A 4xx or 5xx page is still analysed, because it still has headers. |
| `success`         | Whether the site could be fetched at all.                                                             |
| `error`           | Why it could not, when it could not.                                                                  |
| `technologies`    | Every detected technology, most confident first.                                                      |
| `technologyNames` | The same names as a flat list, so CSV and Excel exports stay readable.                                |
| `categories`      | Every category present on the site, deduplicated.                                                     |
| `scriptsFetched`  | How many external script files were downloaded and searched.                                          |
| `detectedAt`      | When the run started, in UTC.                                                                         |

Inside `technologies`, `evidence[].type` tells you where a match came from: `header`, `cookie`, `meta`,
`scriptSrc`, `script` (inside a downloaded JavaScript file), `html`, `text`, `css`, `url`, `dom`, or
`implied` when one technology necessarily brings another (WordPress implies PHP and MySQL).

### How much does it cost to detect a website's tech stack?

This Actor uses **pay per event** pricing: **$0.002 per website analysed**, plus **$0.005 per run start**.
Platform usage is included, so those two numbers are the whole bill.

| Websites in the run | Cost   |
| ------------------- | ------ |
| 10                  | $0.03  |
| 100                 | $0.21  |
| 1,000               | $2.01  |
| 10,000              | $20.01 |

The most-used competing Actor on this Store charges **$0.10 per detection**, so the same 10,000 domains cost
**$1,000** there.

There are Actors on this Store charging a tenth of this, and they return a list of names: no version, no
confidence, and no evidence you can check. This one also makes up to nine HTTP requests per site (the page plus
its scripts) rather than one, which is what finds the bundler, the JavaScript library and the analytics tag that a
single fetch of the HTML misses.

The Apify free plan comes with $5 of monthly platform credit, which covers around 2,000 websites.

To cap spending on a single run, set a maximum cost in the run options, or use the `maxItems` input.

### Tips and advanced options

- **Analyse a deep page, not just the home page.** A checkout URL reveals the payment processor, a blog post
  reveals the CMS plugins. The Actor fetches exactly the URL you give it and does not crawl.
- **Turn off external scripts for a fast, cheap pass.** `maxScripts: 0` cuts each site to one request. You keep
  every header, cookie, meta, HTML and inline-script fingerprint and lose only the ones that live inside bundled
  JavaScript files. Raise it to 30 when you care more about depth than speed.
- **Pay only for what was analysed.** Set `includeFailedUrls: false` and dead domains cost nothing.
- **Cut the noise.** `minConfidence: 100` keeps only technologies backed by a strong or a repeated signal.
- **Exporting to a spreadsheet?** Use `technologyNames` and `categories`. The full `technologies` array is nested
  and flattens badly into CSV.
- **Deduplicate on `finalUrl`.** Two input URLs that redirect to the same site produce two rows.
- **Proxies are optional.** You are fetching your own list of URLs, so there is no anti-bot surface. Turn on Apify
  Proxy datacenter groups only if a site refuses the platform's own IP range.

### What it can and cannot see

The Actor reads what a site sends over HTTP: response headers, cookies, the HTML, the meta tags, the script URLs,
and the contents of the JavaScript files it downloads. Roughly **86% of the fingerprint set** can be evaluated
that way.

The rest cannot, and the Actor does not pretend otherwise. Fingerprints that need a **running JavaScript context**
(a global variable a framework sets at runtime), a **DNS lookup**, or a **TLS certificate issuer** are not
evaluated. In practice this mostly costs you single-page applications that leave no trace in their server-rendered
HTML.

### FAQ, disclaimers and support

**Does this Actor need an account or an API key anywhere?**
No. It fetches public pages with no login of any kind, and it uses no third-party detection API.

**Is it legal to detect a website's technology stack?**
The Actor requests public pages the way a browser does and reads only what the server chooses to send. It does not
log in, does not bypass any access control and does not collect personal data. You are responsible for the list of
URLs you supply and for how you use the results.

**Why did it miss React (or Vue, or Angular) on a site I know uses it?**
Those frameworks are often detected by a variable in the running page, which needs a browser. This Actor is
HTTP-only by design, because that is what makes it fast and cheap. When a framework leaves a trace in the markup
or the script URLs it is found; a fully client-rendered app with a generic bundle name may not be.

**Why is a technology listed that the site does not obviously use?**
Check the evidence. Some fingerprints match a vendor named in a **Content-Security-Policy** header, which proves
the site allows the vendor rather than that it currently loads it. Those rows say `"type": "header", "key":
"content-security-policy"`, so they are easy to filter out. A handful of upstream fingerprints are also just
loose; the evidence field is there so you can tell the difference.

**What does confidence mean?**
It is the fingerprint author's weighting, not a probability. A single weak pattern scores 40 or 50, several
independent patterns add up, and the total caps at 100.

**Why is `version` usually null?**
Only some fingerprints capture a version, and only when the site exposes one, typically in a `?ver=` query string,
a meta generator tag or a server header. Most sites strip those.

**Does it crawl the site?**
No. One request per URL you supply, plus its script files. If you want several pages of one site, put several URLs
in the input.

**Where do the fingerprints come from and how fresh are they?**
From a snapshot of the community Wappalyzer fingerprint set maintained at
[enthec/webappanalyzer](https://github.com/enthec/webappanalyzer), which is licensed GPL-3.0. Wappalyzer's own
repository is gone and its npm package is unmaintained, so the community forks are where the data lives now.
Around 7,600 technologies across 109 categories are covered. The snapshot is refreshed when the Actor is rebuilt.

**Found a bug, or need something else?**
Open an issue on the **Issues** tab of this Actor and it will be picked up. If you need a fingerprint that is
missing, or a browser-based version that can read JavaScript globals, mention it in the issue.

# Actor input Schema

## `startUrls` (type: `array`):

The URLs to detect technologies on. One dataset item comes back per URL. A bare domain works: it is read as https. Paste a list, upload a CSV or text file, or point at a Google Sheet.

## `maxItems` (type: `integer`):

Stop after this many URLs. Set to 0 for no limit. Each URL is one dataset item and one billed detection.

## `maxScripts` (type: `integer`):

How many of the site's external JavaScript files to download and search. Inline scripts are always searched and cost nothing extra. Higher values catch more libraries and bundlers but make each site slower. Set to 0 to skip external scripts.

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

Drop technologies detected below this confidence, from 0 to 100. Leave at 0 to see everything, including weak single-pattern matches.

## `includeFailedUrls` (type: `boolean`):

Write a row for a URL that timed out or refused the connection, with the error in it, so nothing silently disappears from your list. A failed row is still a dataset item and is billed as one. Turn this off to be billed only for sites that were actually analysed.

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

Optional. The Actor fetches the URLs you supply, so there is no anti-bot surface and no proxy is needed for most lists. Turn on datacenter proxies if a site you want blocks the Apify platform's own IP range.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://apify.com"
    },
    {
      "url": "https://wordpress.org"
    },
    {
      "url": "https://www.shopify.com"
    }
  ],
  "maxItems": 1000,
  "maxScripts": 8,
  "minConfidence": 0,
  "includeFailedUrls": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

# 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 = {
    "startUrls": [
        {
            "url": "https://apify.com"
        },
        {
            "url": "https://wordpress.org"
        },
        {
            "url": "https://www.shopify.com"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("thatmike1/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 = { "startUrls": [
        { "url": "https://apify.com" },
        { "url": "https://wordpress.org" },
        { "url": "https://www.shopify.com" },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("thatmike1/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 '{
  "startUrls": [
    {
      "url": "https://apify.com"
    },
    {
      "url": "https://wordpress.org"
    },
    {
      "url": "https://www.shopify.com"
    }
  ]
}' |
apify call thatmike1/tech-stack-detector --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,thatmike1/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/aMpUve01YIegDIrkT/builds/O7awf1vdYIZZbtnOC/openapi.json
