Website Tech Stack Detector: CMS, Analytics, Hosting & Email
Pricing
from $1.50 / 1,000 analyzed domains
Website Tech Stack Detector: CMS, Analytics, Hosting & Email
What any website runs on: CMS, e-commerce, analytics, frameworks, CDN, hosting and email provider. Up to 10,000 domains per run from a list, CSV, Google Sheet or another Actor's dataset. Filter by technology and track stack changes between runs. Failed or blocked sites are free.
Pricing
from $1.50 / 1,000 analyzed domains
Rating
0.0
(0)
Developer
Folt Labs
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a month ago
Last modified
Categories
Security
Limited permissionsShare
Website Tech Stack Detector — CMS, Analytics, Hosting & Email Provider
Find out what any website is built with. Paste a list of domains and get back one clean record per site: CMS, e-commerce platform, analytics and tag managers, JavaScript frameworks, CDN, hosting, web server, payment providers, live chat, marketing tools, and the company's email provider (Google Workspace, Microsoft 365…) from DNS. It's built for sales prospecting and market research: qualify leads by their stack, size up a market, or find every company still on a platform you replace.
Why use it
- 🔎 Fingerprints for 7,600+ technologies, about 6,700 of them detectable without a browser (community Wappalyzer fingerprints plus our own additions)
- 🧾 One record per domain with ready-to-filter fields:
cms,ecommercePlatform,analytics,tagManagers,jsFrameworks,cdn,hosting,emailProvider… - 📬 Email & DNS intelligence: mailbox provider, email security gateway, DNS provider, and SaaS tools that verified the domain (Atlassian, Stripe, HubSpot, Zoom…)
- 📥 Import from anywhere: paste domains, link a CSV / TXT file or a Google Sheet, or pick the dataset of another Actor run (for example Google Maps Scraper results)
- 🎯 Filter by technology: output only the sites that use Shopify, WordPress, HubSpot, any CMS…
- 🔔 Track stack changes: run the same list on a schedule and see which sites added or removed technologies since the last run
- ⚡ Fast and cheap: one request per site, no browser. 500 domains took about 2 minutes in our test, at default memory
- 💸 Fair billing: sites that are down, return an error, or show a bot-protection challenge are returned free, clearly marked
- 🛡️ Safe by design: only public websites are fetched; IP addresses, internal hostnames and private networks are refused
Use cases
- Sales lead qualification: keep only prospects on Shopify, WordPress, HubSpot or Microsoft 365, or skip those already using a competitor
- Competitor and market research: measure platform share across a list of companies in a niche or country (the run's
SUMMARYlists the most common technologies) - Agency prospecting: find sites on outdated or DIY stacks (old jQuery, page builders, no tag manager) that could use a rebuild or migration
- Security and asset inventory: list the CMS, CDN, hosting, web server and email setup across your own company's domains
- Local business prospecting: take the websites from a Google Maps Scraper run and keep the businesses on WordPress or Wix
- Switch signals: monitor a list of prospects or customers weekly and catch the ones that moved to (or away from) a platform
How to use
- Add domains (
example.com) or URLs (https://example.com/shop), one per line, up to 10,000 per run. Or import them from a file, a Google Sheet or a dataset (see below). - Keep Check DNS on to get the email provider, DNS provider and DNS-verified SaaS tools.
- Turn on Include evidence to see which header, script, meta tag or DNS record each detection is based on.
- Optionally limit the output to sites using certain technologies, or turn on change monitoring.
- Run it, then filter the dataset or export it to CSV / Excel / Google Sheets.
Input example
{"domains": ["allbirds.com", "wordpress.org", "https://nextjs.org", "example-store.com"],"checkDns": true,"includeEvidence": false,"maxConcurrency": 20}
A bare domain means its homepage (we try https://domain, then https://www.domain, then http://domain). A URL with a path is analyzed as given. Duplicates (example.com, https://www.example.com/) are analyzed once.
Import from a file or a Google Sheet
{"domains": [],"domainsFileUrl": "https://docs.google.com/spreadsheets/d/<your-sheet-id>/edit#gid=0"}
- Works with a public link to a CSV or TXT file or a Google Sheet shared as Anyone with the link can view (the tab in the link is used).
- The column named
domain,website,urlorhomepageis used, in that order of preference (a header such as Company Website or Website URL also works). Without one, the first column is used. - Comma, semicolon and tab separated files are detected automatically. Excel files (
.xlsx) need to be exported as CSV first. Max 5 MB and 10,000 rows. - The file host's
robots.txtis respected. A sheet that isn't public gives a clear error instead of an empty run.
Import from another Actor's dataset (e.g. Google Maps Scraper)
{"domains": [],"datasetId": "<dataset ID>","datasetField": "website"}
- Pick the dataset in the input form (the Actor gets read-only access to it) or paste its ID.
datasetFieldis optional: without it, the first ofdomain,website,url,homepagethat has values is used. Google Maps results usewebsite. - To chain runs automatically, add this Actor as an integration of the scraper's task and set
"datasetId": "{{resource.defaultDatasetId}}". - Up to 10,000 items are read. Items without a website are skipped. Links to social profiles (
facebook.com/yourshop,instagram.com/…,linkedin.com/company/…) are reported as invalid instead of being analyzed, because the stack of Facebook says nothing about the business.
Domains from the list, the file and the dataset are combined (remove the example domains from the list if you only want the imported ones). Full URLs from files and datasets are reduced to the homepage, so https://www.example.com/contact?utm_source=maps and example.com are analyzed once. inputUrl keeps the original value, so you can join the results back to your sheet or dataset. Up to 10,000 unique domains per run; extra ones are skipped and reported in the SUMMARY.
Only sites using certain technologies
{"domainsFileUrl": "https://example.com/leads.csv","onlyWithTechnologies": ["Shopify", "WooCommerce", "Magento"]}
Names are technology or category names exactly as they appear in the output (case does not matter), for example HubSpot, Microsoft 365, Ecommerce, CMS or Live chat. A site is output when it uses any of them. Misspelled names are reported in the log with suggestions; if none of the names is known, the run stops before analyzing anything.
Billing with the filter: you pay per analyzed domain; the filter only trims the output. Every domain that was analyzed is charged, including the ones the filter leaves out. Failed and blocked domains stay free. The SUMMARY shows how many matched and how many were filtered out.
Track technology changes
{"domainsFileUrl": "https://example.com/prospects.csv","monitorChanges": true,"stateKey": "prospects","outputOnlyChanges": true}
Schedule this weekly. Each record gets changeStatus (new, changed or unchanged), technologiesAdded and technologiesRemoved compared with the previous run that used the same stateKey. The first run marks every domain as new. With outputOnlyChanges, only new and changed domains are written to the dataset.
Billing with monitoring: every analyzed domain is charged, also when it is unchanged and outputOnlyChanges hides it. Failed and blocked domains stay free and keep their previous state.
Output
One record per domain. This is a real record from a run on 2026-10-06 (the technologies list is shortened here; the run returned 15):
{"domain": "allbirds.com","inputUrl": "www.allbirds.com","finalUrl": "https://www.allbirds.com/","statusCode": 200,"title": "Allbirds: Comfortable, Sustainable Shoes & Apparel","technologies": [{ "name": "Shopify", "categories": ["Ecommerce"], "version": null, "confidence": 100 },{ "name": "Cloudflare", "categories": ["CDN"], "version": null, "confidence": 100 },{ "name": "Google Tag Manager", "categories": ["Tag managers"], "version": null, "confidence": 100 },{ "name": "PayPal", "categories": ["Payment processors"], "version": null, "confidence": 100 },{ "name": "Apple Pay", "categories": ["Payment processors"], "version": null, "confidence": 100 },{ "name": "Attentive", "categories": ["Personalisation"], "version": null, "confidence": 100 },{ "name": "Microsoft 365", "categories": ["Webmail", "Email"], "version": null, "confidence": 100 },{ "name": "DocuSign", "categories": ["Miscellaneous"], "version": null, "confidence": 100 }],"technologyCount": 15,"cms": null,"ecommercePlatform": "Shopify","analytics": [],"tagManagers": ["Google Tag Manager"],"jsFrameworks": [],"cdn": "Cloudflare","hosting": null,"webServer": null,"paymentProcessors": ["Apple Pay", "PayPal"],"marketingAutomation": [],"liveChat": [],"emailProvider": "Microsoft 365","emailSecurityGateway": null,"dnsProvider": null,"mxHosts": ["allbirds-com.mail.protection.outlook.com"],"nameservers": ["ns2.markmonitor.com", "ns3.markmonitor.com", "ns4.markmonitor.com", "ns5.markmonitor.com", "ns6.markmonitor.com", "ns7.markmonitor.com"],"blocked": false,"blockedBy": null,"error": null,"checkedAt": "2026-10-06T15:24:23.171Z"}
With Include evidence, each technology also lists what matched (real output from the same run):
{"name": "Shopify","categories": ["Ecommerce"],"version": null,"confidence": 100,"evidence": ["headers: powered-by: Shopify","meta: shopify-digital-wallet=/11044168/digital_wallets/dialog","scriptSrc: https://cdn.shopify.com/s/files/1/1104/4168/files/jsEncrypt.js?v=1756910700","scriptSrc: https://www.allbirds.com/cdn/shop/t/4159/assets/theme2.js","scripts: Shopify.shop = \"","dns:CNAME: shops.myshopify.com"]}
A site behind a bot-protection challenge comes back marked, with no technologies, and is not charged (real record):
{"domain": "canva.com","inputUrl": "www.canva.com","finalUrl": "https://www.canva.com/","statusCode": 403,"title": "Canva","technologies": [],"technologyCount": 0,"blocked": true,"blockedBy": "Cloudflare","error": "blocked by bot protection (Cloudflare: cf-mitigated: challenge)","checkedAt": "2026-10-06T15:24:34.329Z"}
| Field | Meaning |
|---|---|
domain / inputUrl / finalUrl | Host without www. / what you entered / page reached after redirects |
statusCode, title | HTTP status and <title> of the analyzed page |
technologies | Every detection: name, categories, version (when the site exposes it), confidence (0–100), evidence (optional) |
cms, ecommercePlatform | Main CMS / shop platform seen on the website |
analytics, tagManagers, jsFrameworks | Lists of analytics tools, tag managers, JavaScript frameworks |
cdn, hosting, webServer | Infrastructure, from response headers and the site's DNS CNAME (not from scripts loaded off a CDN) |
paymentProcessors, marketingAutomation, liveChat | Lists of payment, marketing-automation and chat tools |
emailProvider | Mailbox provider from MX records. If MX points to a security gateway, it is read from SPF when possible |
emailSecurityGateway | Proofpoint, Mimecast, Cisco, Barracuda… if MX points to one |
dnsProvider, mxHosts, nameservers | DNS hosting and the raw MX / NS hostnames |
technologyCount | Number of technologies |
blocked, blockedBy | true + vendor (Cloudflare, DataDome, HUMAN, Imperva, Akamai, AWS WAF, Vercel…) when a challenge page was served |
error | Why a domain could not be analyzed (DNS, timeout, HTTP 4xx/5xx, blocked, invalid input), or null |
changeStatus, technologiesAdded, technologiesRemoved | Only with Monitor technology changes: new / changed / unchanged and what changed since the last run (null when the site could not be analyzed this time) |
DNS-based fields are null / empty when Check DNS is off. Technologies found only in DNS (SPF includes, domain-verification records such as stripe-verification= or atlassian-domain-verification=) mean the company has an account with that vendor. They don't mean the tool runs on the homepage, and they never fill the website fields (cdn, paymentProcessors…).
Pricing
Pay per result: $1.50 per 1,000 analyzed domains ($0.0015 each), plus $0.002 per run. Platform usage is included. A domain is charged only when its website answered successfully (2xx/3xx) and was analyzed. Domains that don't resolve, time out, return an HTTP error, are invalid, or show a bot-protection challenge are returned with error / blocked and are not charged. So 1,000 domains where 800 answer cost about $1.20. Cap any run with Apify's maximum cost per run, and the Actor stops cleanly.
The price does not depend on the options. You pay per analyzed domain; the technology filter and "output only changes" only trim the output, so analyzed domains they hide are charged too. Importing from a file or a dataset costs nothing extra.
If a list turns out to be almost entirely dead (more than 80% of at least 200 checked domains fail), the run stops early with a message instead of working through the rest. Nothing is charged for the failed domains or for the ones not checked.
Accuracy
We checked the Actor against an independent script on a fixed list of 50 well-known sites. The script uses plain string and header checks such as /wp-content/, __NEXT_DATA__, the cf-ray header, x-vercel-id and MX records. On the 44 sites both could load, across 21 technologies including WordPress, Shopify, Next.js, Cloudflare, Fastly, Vercel, Google Tag Manager and OneTrust:
- Precision 97%: 106 detections confirmed, 3 not confirmed.
- Recall 97%: 106 known technologies found, 3 missed.
- Email provider: right on 27 of 27 Google Workspace / Microsoft 365 domains.
We also used this list while tuning the detector, so expect somewhat lower accuracy on other sites — especially ones that load their tools through a tag manager (see Limitations).
Of the six disagreements:
- Two were content served differently to our servers than to the checking script.
- Two were tools the page only preconnects to.
- One was a WordPress site used headless.
- One was a Google Tag Manager comment the script doesn't count.
Limitations
- No JavaScript rendering. We read the HTML, headers and DNS of one response. Tools that are injected later by another script, most often tags loaded through Google Tag Manager, and apps that build their page entirely in the browser can be missed. Script URLs written into the page's inline snippets are detected.
- One page per input: the homepage, or the URL you give. Technologies used only on other pages (checkout, blog) aren't seen.
- Bot-protected sites (Cloudflare challenge, DataDome, HUMAN, Imperva…) are reported as
blocked, free of charge, instead of being bypassed. Requests come from Apify's cloud servers, which some sites challenge more often than home connections. - Versions are reported only when the site exposes them (meta generator, file names, attributes).
- Content can differ by country: sites may serve different tags to visitors in different regions, and the Actor sees the version served to Apify's servers.
- Fingerprints are a snapshot: very new or niche tools may be missing. We update the bundled dataset periodically.
- Only public websites on standard ports (80/443) are analyzed. IP addresses and internal or private hostnames are refused.
- Change monitoring compares technologies with confidence 50 or more. Technologies known only from DNS (email provider, TXT verifications) are compared only when both runs had Check DNS on and every DNS query answered. Sites can also rotate scripts or A/B tests, which then shows up as a change. Runs of the same monitoring name should not overlap. A domain not seen for 180 days is forgotten.
- Each domain gets 25 seconds in total, across all fallback attempts, to answer. Very slow sites are reported as
timed out(free).
FAQ
How is this different from browser extensions? It runs on thousands of domains at once, returns structured data you can filter and export, and adds DNS insight (email provider, DNS-verified SaaS tools) that page-only tools don't show.
Do you store cookies or personal data? No. Only cookie names (e.g. _shopify_y) are used for detection, and their values are never kept. The Actor reads public website and DNS data only.
What counts as "blocked"? A recognized challenge or block page from a bot-protection vendor. A plain error such as HTTP 404 or 500 is reported in error with blocked: false. Neither is charged.
How long do 10,000 domains take? In our test, 500 domains took 2.3 minutes at default settings, which is about 45 minutes per 10,000. Lists with many dead domains take longer, because a site that doesn't answer can take up to 25 seconds; domains that don't exist in DNS usually fail within a second or two. A higher Max concurrency makes it faster (values below 10 are raised to 10). The default memory (1 GB) is enough and is also the maximum: our 500-domain test peaked at about 400 MB.
Does the technology filter make the run cheaper? No. Every analyzed domain is charged whether or not it matches; the filter saves you sorting the results. Failed and blocked domains are free either way.
Why did my run stop early? More than 80% of the first 200+ domains could not be fetched (domain doesn't exist, no answer, HTTP error). That usually means the wrong column was imported, or the list is old. The message and the SUMMARY (stoppedEarly) say how many failed. Check the input and run again; failed domains were not charged.
Can I use the results of Google Maps Scraper or another Actor? Yes: pick its dataset in Dataset with websites, or connect the two with an integration and pass {{resource.defaultDatasetId}}. Social-media profile links are skipped as invalid; real websites are reduced to their homepage and deduplicated.
Where is the monitoring memory stored? In a key-value store named tech-stack-detector-state in your own Apify account (one set of records per monitoring name). It holds only domain names, technology names and the date last seen. Delete it to start over.
Where do the fingerprints come from? From webappanalyzer, the open community continuation of the Wappalyzer fingerprints (GPL-3.0), pinned to a fixed version, plus Folt Labs additions for HTML-visible framework markers and email, DNS and hosting providers.
Credits & license
Technology fingerprints © the webappanalyzer contributors (enthec/webappanalyzer, commit eea872a), licensed under the GNU General Public License v3.0. The dataset files are bundled unmodified with their license. Our additions, plus one pattern we switched off because it was misleading, live in a separate file. Category names come from the same dataset.