# Website Tech Stack Detector: CMS, Frameworks & Hosting (`vantum/website-tech-stack-detector`) Actor

Find the CMS, ecommerce platform, frameworks, analytics, hosting, CDN, email provider and 6,700+ other technologies behind any list of websites. One row per site, with versions and evidence.

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

## Pricing

$1.20 / 1,000 website analyseds

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

## Website Tech Stack Detector

Paste a list of websites and get **one row per site** listing what it runs on:

- **CMS and ecommerce platform**, e.g. WordPress 7.2, Shopify, Magento, Wix, Webflow or Squarespace;
- **frameworks and libraries**, e.g. Next.js, Nuxt, React, Vue, Angular, Astro, SvelteKit, jQuery 3.7.1 or Bootstrap 5.3.2;
- **marketing and sales tools**: analytics, tag managers, ad pixels, marketing automation, CRM, live chat, A/B testing, reviews, cookie consent and payments;
- **infrastructure**: hosting and PaaS, CDN, web server, reverse proxy, language, caching, security/WAF and the TLS certificate issuer and expiry;
- **email setup from DNS**: email provider (Google Workspace, Microsoft 365…), sending services in SPF (SendGrid, Mailchimp, HubSpot, Zendesk…), domain verifications (which SaaS tools the company verified) and nameservers;
- **company details**: page title, description, language, social profiles (LinkedIn, X, Facebook, Instagram, YouTube, TikTok, GitHub, Pinterest) and `mailto:` emails.

Each technology comes with its **version** when the site exposes it, a **confidence** score, its **categories**, its **CPE** (for security teams), the **evidence** that matched and which technology **implied** it.

Detection uses the open-source **webappanalyzer** fingerprint set: 7,600+ technologies in the format popularised by Wappalyzer, pinned to a known commit. It runs over plain HTTP and DNS with no browser, so it is fast. **$1.20 per 1,000 websites.** Sites that fail, block the request or are not HTML pages are **free**, and so are dry runs.

### Why this one

| | This Actor | Typical tech-stack actors |
|---|---|---|
| Price | **$0.0012 per website**, no start fee | $0.0015–$0.10 per website, often plus a start fee |
| Failed, blocked and non-HTML sites | **free** | often charged as a result row |
| Fingerprints | 7,600+, pinned open-source set, named in every run | varies, often undisclosed or an old snapshot |
| Version numbers and confidence | ✅ | some |
| Evidence for every match (header, script URL, meta tag, DOM element, cookie, DNS record) | ✅ | ❌ |
| `implies` / `requires` / `excludes` rules (e.g. WooCommerce only with WordPress) | ✅ | rarely |
| Email provider, SPF senders, domain verifications from DNS | ✅ free | ❌ or a separate actor |
| TLS issuer and expiry date | ✅ | ❌ |
| Social profiles and emails | ✅ | some |
| Flat columns per category (CMS, analytics, hosting…) for CSV/Excel | ✅ 30 columns | some |
| Change tracking between scheduled runs (added / removed technologies) | ✅ | ❌ |
| Free dry run (validate, de-duplicate, maximum cost) | ✅ | ❌ |
| Bulk input: bare domains, URLs, uploaded files, duplicates removed | ✅ | partial |

### Quick start

1. Paste websites, one per line (**Bulk edit** for long lists): `example.com`, `www.example.com` or `https://example.com/shop` all work.
2. Run. Open the **Overview**, **Marketing & sales stack**, **Infrastructure** or **Company & contacts** tab, or download all rows as CSV, Excel or JSON.

```json
{
  "websites": ["https://wordpress.org/news/", "nextjs.org", "books.toscrape.com"],
  "checkDns": true,
  "minConfidence": 50
}
```

From the API, `startUrls: [{ "url": "https://a.com" }]`, `urls` and `domains` are accepted too.

### Use cases

- **Lead generation:** find every Shopify store, or every site on HubSpot, in a list of prospects. Filter the CSV by the `ecommerce`, `crm` or `marketingAutomation` column.
- **Sales intelligence:** know a prospect's stack before the call: CMS, analytics, chat, payments, email provider.
- **Competitor monitoring:** schedule a weekly run with **Track changes** and get a `changes` field when a competitor adds or drops a tool.
- **Security and IT inventory:** versions plus CPE identifiers for the web-facing software of many domains, and certificate expiry dates.
- **Market research:** `RUN_SUMMARY` lists the 50 most common technologies across your list.

### How it works

- For each website it fetches the URL you gave, usually the home page, following up to 10 redirects. With no `https://` given it tries HTTPS first, then HTTP if HTTPS cannot connect.
- At the same time it reads the domain's DNS records (MX, TXT, NS, SOA, CNAME). DNS is checked on the host you gave without `www.`.
- It matches the response headers, cookies, HTML, script URLs, inline scripts and styles, meta tags, DOM elements, visible text, DNS records and the TLS certificate issuer against the fingerprints. Then it applies the fingerprints' `implies`, `requires` and `excludes` rules. Versions come from the matched values; when several are found, the highest is reported.
- A browser would also see JavaScript globals. Instead, a global counts as present when an inline script assigns it (`window.X =`, `var X =`). A small supplement adds the static markers of server-rendered frameworks (for example `__NEXT_DATA__` and `/_next/static/` for Next.js, `/_astro/` for Astro, `/_app/immutable/` for SvelteKit). The upstream file is never changed.
- Hints the fingerprint set marks with confidence 0 are dropped. **Minimum confidence** (default 50) hides weak single hints.
- Up to **Max concurrency** websites (default 10) run in parallel. Each is a different server and gets only one or two requests.
- If the run is about to time out, it stops starting new websites and still writes the summary. After a platform migration it resumes without re-checking, or re-charging, finished websites.

#### Statuses (only `ok` is charged)

| `status` | Meaning | Charged |
|---|---|---|
| `ok` | Page fetched (HTTP < 400, HTML) and analysed | ✅ $0.0012 |
| `blocked` | 401/403/429 or a bot challenge page (Cloudflare, DataDome, Akamai…) | free |
| `httpError` | Other HTTP error (404, 500…) | free |
| `notHtml` | The URL is a PDF, image, JSON… | free |
| `failed` | DNS error, timeout, TLS error, connection refused, redirect loop | free |
| `dryRun` | Dry run row | free |

Free rows still include whatever the headers, TLS certificate and DNS revealed, for example Cloudflare, the email provider or the hosting.

### Output

One dataset row per website. Abridged example (real output from wordpress.org):

```json
{
  "url": "https://wordpress.org/news/",
  "domain": "wordpress.org",
  "status": "ok",
  "statusCode": 200,
  "title": "WordPress News – The latest news about WordPress and the WordPress community",
  "language": "en-US",
  "technologyCount": 14,
  "technologies": [
    {
      "name": "WordPress", "version": "7.2", "confidence": 100, "categories": ["CMS", "Blogs"],
      "website": "https://wordpress.org", "cpe": "cpe:2.3:a:wordpress:wordpress:*:*:*:*:*:*:*:*",
      "evidence": [
        "html: <link rel='stylesheet' id='wp-block-navigation-css' href='https://wordpress.org/news/wp-includes/",
        "header link: <https://wordpress.org/news/wp-json/>; rel=\"https://api.w.org/\""
      ],
      "impliedBy": null
    },
    { "name": "PHP", "version": null, "confidence": 100, "categories": ["Programming languages"], "impliedBy": ["WordPress"] },
    { "name": "Google Tag Manager", "confidence": 100, "evidence": ["html: googletagmanager.com/ns.html?id=GTM-…"] }
  ],
  "cms": ["WordPress 7.2"],
  "pageBuilders": ["WordPress Block Editor", "WordPress Site Editor"],
  "tagManagers": ["Google Tag Manager"],
  "webServers": ["Nginx"],
  "programmingLanguages": ["PHP"],
  "databases": ["MySQL"],
  "mxRecords": ["smtp1-dca.wordpress.org", "smtp2-dca.wordpress.org"],
  "spfSenders": ["Help Scout"],
  "domainVerifications": ["google"],
  "tlsIssuer": "Let's Encrypt",
  "tlsExpires": "2026-12-22",
  "socialProfiles": { "x": "https://www.x.com/WordPress", "linkedin": "https://www.linkedin.com/company/wordpress" },
  "charged": true,
  "checkedAt": "2026-10-31T19:57:04Z"
}
```

A Shopify store from the same test run (www.allbirds.com) came back with `ecommerce: ["Shopify"]`, `payments: ["Apple Pay", "PayPal", "Shop Pay"]`, `cdn: ["Cloudflare"]`, `emailProvider: "Microsoft 365"` and evidence such as `script: https://cdn.shopify.com/…` and `inline script sets ShopifyAnalytics`.

**Flat category columns** (array of names, with version when known, or null): `cms`, `ecommerce`, `blogs`, `pageBuilders`, `staticSiteGenerators`, `jsFrameworks`, `webFrameworks`, `jsLibraries`, `uiFrameworks`, `analytics`, `tagManagers`, `advertising`, `retargeting`, `marketingAutomation`, `crm`, `liveChat`, `payments`, `abTesting`, `cookieConsent`, `reviews`, `cdn`, `hosting`, `webServers`, `reverseProxies`, `programmingLanguages`, `databases`, `security`, `caching`, `fonts`, `sslCertificateAuthority`. Every technology is in `technologies`, including categories without a flat column.

**DNS fields** (with *Check DNS*): `emailProvider`, `emailProviders`, `mxRecords`, `spfSenders`, `spf`, `domainVerifications`, `nameservers`.

**Other fields:** `finalUrl`, `error`, `description`, `generator`, `tlsIssuer`, `tlsExpires`, `redirects`, `socialProfiles`, `emails`, `responseTimeMs`, `changes` (with change tracking) and `charged`.

The **RUN_SUMMARY** record holds counts by status, charged websites, the 50 most common technologies, skipped inputs and the fingerprint commit used.

#### Change tracking

Turn on **Track changes** and schedule the Actor. Each website's technology list is saved in a named key-value store (`tech-stack-history` by default; it persists between runs). Each `ok` row then gets:

```json
"changes": { "added": ["Klaviyo"], "removed": ["Mailchimp"], "changed": true, "previousCheckAt": "2026-10-24T06:00:02Z" }
```

The first check of a website has `"firstCheck": true`. Use a different **History store name** for each watch list. Changes compare technologies at or above your minimum confidence, so keep that setting the same between runs.

### Input

| Field | Default | Description |
|---|---|---|
| `websites` | – | Websites or domains, one per line (also `startUrls`, `urls` or `domains` via API). Up to 100,000 per run. |
| `checkDns` | `true` | Read MX/TXT/NS/SOA/CNAME for the email provider, SPF senders, verifications and DNS-detected technologies. |
| `minConfidence` | `50` | Hide technologies below this confidence (1–100). |
| `includeEvidence` | `true` | Add `evidence` and `impliedBy` to each technology. |
| `includeContacts` | `true` | Add social profiles and `mailto:` emails. |
| `trackChanges` | `false` | Add `changes` versus the previous run. |
| `historyStoreName` | `tech-stack-history` | Named store used by change tracking. |
| `dryRun` | `false` | Validate and de-duplicate the list and report the maximum cost. Visits nothing and charges nothing. |
| `maxConcurrency` | `10` | Websites checked in parallel (1–50). |
| `requestTimeoutSecs` | `25` | Per-website timeout. |
| `userAgent` | Chrome desktop | Custom User-Agent. |

### Pricing

**Pay per event: $0.0012 per analysed website ($1.20 per 1,000).** No start fee and no monthly rental. Blocked, failed, HTTP-error and non-HTML websites are free, and so are dry runs and DNS lookups. Set a maximum cost per run in the run options: the Actor stops cleanly when it reaches it.

### Limitations

- **One page per website.** It checks the page you give, usually the home page. Tools used only on other pages (for example a checkout provider) are found only if you give that page's URL.
- **No browser.** Technologies that are visible only after JavaScript runs, and add no markers to the HTML, headers or DNS, are not detected. Those are about 900 of the 7,600 fingerprints, mostly ones keyed only on runtime globals; the inline-script heuristic recovers some of them.
- Sites behind aggressive bot protection may return `blocked`. Those rows are free and still show what the headers and DNS reveal.
- Like every fingerprint-based detector, results are evidence-based guesses. Check the `evidence` field when a match matters.

### Fingerprint data and license

Technology fingerprints come from **[enthec/webappanalyzer](https://github.com/enthec/webappanalyzer)**, licensed **GPL-3.0**. They are downloaded unmodified at a pinned commit when the Actor is built, and the license is included in the image (`data/LICENSE-webappanalyzer-GPL-3.0.txt`). The `RUN_SUMMARY` record names the commit used. The detection engine, the static-marker supplement and the email-provider mapping are this Actor's own. This Actor is not affiliated with Wappalyzer, BuiltWith or enthec.

### FAQ

**Is this the same as Wappalyzer?** It uses the same open fingerprint format and an actively maintained community fork of that dataset, but it is an independent engine. Wappalyzer's browser extension also sees runtime JavaScript; this Actor trades that for speed and price.

**Can I check thousands of domains?** Yes: paste them with Bulk edit or send them via the API. In the test run, 6 websites took 2.5 seconds in total at the default concurrency of 10.

**Why does a site show `blocked`?** It refused automated requests. You are not charged. Try again later or with a lower concurrency.

**Does it store anything in my account?** Only the dataset and RUN_SUMMARY for each run, plus the named history store if you turn on change tracking.

# Actor input Schema

## `websites` (type: `array`):

Domains or URLs, one per line (use Bulk edit to paste thousands). example.com, www.example.com and https://example.com/shop all work; duplicates are removed. Each website = one request to the page you give (usually the home page). API users can also send startUrls: \[{url}] or urls / domains.

## `checkDns` (type: `boolean`):

Also reads the domain's MX, TXT, NS, SOA and CNAME records: email provider (Google Workspace, Microsoft 365…), email-sending services in SPF (SendGrid, Mailchimp, HubSpot…), domain verifications (which SaaS tools the company uses) and DNS-detectable technologies. Free and fast.

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

Only report technologies with at least this confidence (0-100). Some fingerprints are weak hints worth 25 or 50; 50 keeps likely matches, 100 keeps only certain ones, 1 shows everything.

## `includeEvidence` (type: `boolean`):

Add, for each technology, what matched (header, script URL, meta tag, cookie, DNS record…) and which other technology implied it.

## `includeContacts` (type: `boolean`):

Add the company's social profiles (LinkedIn, X, Facebook, Instagram, YouTube, TikTok, GitHub, Pinterest) and mailto: addresses found on the page.

## `trackChanges` (type: `boolean`):

Remember each website's stack in a named key-value store and add a `changes` field with technologies added or removed since the last run. Schedule the Actor (e.g. weekly) to monitor competitors or leads.

## `historyStoreName` (type: `string`):

Named key-value store in your account used by change tracking. Use different names to keep separate watch lists.

## `dryRun` (type: `boolean`):

Validate and de-duplicate the list and show the maximum cost without visiting any website. Nothing is charged.

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

Websites checked in parallel. Each website is a different server, so this is polite to every site.

## `requestTimeoutSecs` (type: `integer`):

Give up on a website after this long. Timed-out websites are not charged.

## `userAgent` (type: `string`):

Optional custom User-Agent header. Default: a current desktop Chrome.

## Actor input object example

```json
{
  "websites": [
    "https://wordpress.org/news/",
    "nextjs.org",
    "books.toscrape.com"
  ],
  "checkDns": true,
  "minConfidence": 50,
  "includeEvidence": true,
  "includeContacts": true,
  "trackChanges": false,
  "historyStoreName": "tech-stack-history",
  "dryRun": false,
  "maxConcurrency": 10,
  "requestTimeoutSecs": 25
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `marketing` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `allFields` (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 = {
    "websites": [
        "https://wordpress.org/news/",
        "nextjs.org",
        "books.toscrape.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("vantum/website-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 = { "websites": [
        "https://wordpress.org/news/",
        "nextjs.org",
        "books.toscrape.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("vantum/website-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 '{
  "websites": [
    "https://wordpress.org/news/",
    "nextjs.org",
    "books.toscrape.com"
  ]
}' |
apify call vantum/website-tech-stack-detector --silent --output-dataset

```

## MCP server setup

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