I wanted a fast way to answer "what's this site built with?" — CMS, ecommerce platform, JS framework, analytics tags, CDN, payment/chat widgets — without spinning up a full headless browser per URL.
The approach
One plain HTTP fetch of the homepage via Crawlee's CheerioCrawler, then check three things against a signature table:
- Response headers (
Server,X-Powered-By,CF-Ray,X-Vercel-Id, etc.) - The
<meta name="generator">tag -
<script src>domains on the page
Same general idea as Wappalyzer, just a much smaller, hand-written signature set — about 30 technologies across 7 categories (CMS, ecommerce, JS frameworks, analytics, CDN/hosting, payment, live chat). Each signature is a test(context) function:
{
name: 'WordPress',
category: 'cms',
test: ({ generator }) => generator.includes('wordpress'),
}
context gives you lowercased headers, html, scriptSrcs, and generator — cheap to check, no DOM traversal needed for most signatures.
What it deliberately doesn't do
- No headless browser. Everything it reads is already served to any visitor's plain HTTP request — no JS execution, no login, no bypassing any protection.
- No guessing. If nothing in the signature table matches, it returns an empty result for that category rather than a fuzzy best-guess. A smaller-but-honest signature set beats a big one that's confidently wrong.
Where it fell short (and stayed that way, on purpose)
Detection accuracy is bounded by however many signatures I've hand-written — it's ~30 technologies, not the hundreds Wappalyzer tracks. Sites using something niche just come back with an empty category instead of a wrong guess. That's a real limitation, not a bug, and the tradeoff I'd make again: adding a signature is one function, so it grows exactly as fast as real usage demands it, not ahead of it.
Try it
- Source: github.com/timmKal01/website-tech-stack-detector
- Hosted version (Apify actor, no setup): apify.com/m_ctim/website-tech-stack-detector
If you've got a signature I'm missing, signatures.js is a small file — happy to take PRs or just hear about the gap.
Top comments (0)