🔍Auto-DetectWPWordPressSHShopifyDRDrupalJOJoomlaMOMoodlePSPrestaShopHSHubSpotOCOpenCartGHGhost
tutorials

How to Identify Any Website's CMS From Its Source Code

The exact markers that identify WordPress, Shopify, Ghost, HubSpot, Drupal, Joomla, Moodle and more in page source — in the order to check them.

TW
The Websites Detector Team
Published Aug 29, 20266 min read
📖

Every content management system leaves fingerprints in the HTML it produces. Once you know what to look for, identifying a platform takes about thirty seconds and needs nothing but your browser.

This is the checklist we use in our own detector, in the order we run it. The order matters more than most guides admit, and we explain why.

Open the source

Right-click any page and choose View Page Source (Ctrl+U / Cmd+Option+U). This shows the raw HTML the server sent, which is what you want. "Inspect Element" shows the DOM after JavaScript has modified it — useful for other things, misleading here.

Then use Ctrl+F / Cmd+F to search for the markers below.

Step 1: Check the generator tag first

Search for:

name="generator"

Many platforms declare themselves outright:

<meta name="generator" content="WordPress 6.5" />
<meta name="generator" content="Ghost 5.87" />
<meta name="generator" content="Drupal 10 (https://www.drupal.org)" />
<meta name="generator" content="Joomla! - Open Source Content Management" />
<meta name="generator" content="Moodle 4.3" />

Check this before anything else. Every other test on this page is a heuristic that infers a platform from a side effect, and any of them can fire on a site that merely embeds or links to another platform. The generator tag is the CMS naming itself.

We learned this the expensive way — our detector once reported a Ghost publication as Shopify because it ran its pattern list before the generator check. The full write-up is here, and the fix was simply reordering two blocks.

Two caveats. Security plugins often strip the generator tag, so its absence proves nothing. And Shopify never emits one at all, so a missing tag is perfectly normal on a real store.

Step 2: Platform-specific markers

If there is no generator tag, work through these. They are ordered from most specific to most generic — check in this order, because the last few produce false positives on custom builds.

WordPress

/wp-content/
/wp-includes/
/wp-json/

The most reliable fingerprint on the web. Nearly every WordPress page loads a stylesheet or script from /wp-content/, and it appears dozens of times in the source.

Bonus: you get the theme name for free. The path is structured:

<link rel="stylesheet" href="/wp-content/themes/astra/style.css" />

That site runs Astra. WordPress requires every theme's style.css to begin with a header comment declaring name, version, author and license — so if you open that file directly, you get full metadata. This is why WordPress theme detection is close to a solved problem and our WordPress detector can return version and author, while most other platforms only yield a name.

Plugins are visible the same way, at /wp-content/plugins/<plugin-name>/.

Ghost

<meta name="generator" content="Ghost
/assets/built/

Ghost is easy to confirm and hard to read further. The compiled bundle in /assets/built/ is named after the theme for exactly two official themes — source.js and casper.js. Everything else ships a generic main.min.js and genuinely cannot be identified from public HTML.

Shopify

Shopify.theme
Shopify.shop
cdn.shopify.com
/cdn/shop/

Look for the inline runtime object:

<script>var Shopify = Shopify || {};
Shopify.theme = {"name":"Dawn","id":123456789,"role":"main"};</script>

That gives you the theme name, its ID, and whether it is the live theme or a preview.

Do not match on a bare .myshopify.com string. Plenty of non-Shopify sites link to a Shopify store — that is precisely the false positive described above. Require a runtime object or an asset host.

HubSpot

hs_cos_wrapper
hubfs/raw_assets
hub_generated

Deliberately not js.hs-scripts.com or a bare hs-sites.com link — both appear on any WordPress site with a single embedded HubSpot form. The three markers above only appear when HubSpot is rendering the page itself.

Theme names appear only for marketplace themes, at raw_assets/public/<theme-name>/. Agency-built portals compile each module separately and never name a theme, which is a real limitation rather than a detection failure.

Moodle

M.cfg
/theme/styles.php

Moodle sets a global M.cfg JavaScript object on every page. Distinctive and reliable.

Drupal

/sites/default/files/
/sites/all/
/themes/custom/
data-drupal-selector

Drupal 8+ also emits data-drupal-* attributes throughout the markup.

PrestaShop

var prestashop
/themes/<name>/assets/

The inline var prestashop object is the strong signal. The theme path alone is not.

OpenCart

index.php?route=common/home
route=product/product
catalog/view/theme/

OpenCart's route= query parameter structure is distinctive and usually appears in navigation links.

Joomla

/media/jui/
/media/system/js/
/templates/<name>/css/

Check Joomla after the platforms above. Its /templates/ convention overlaps with PrestaShop and OpenCart, and with plenty of hand-built sites.

Step 3: When nothing matches

A clean source with no recognisable markers usually means one of these:

A static site generator. Hugo, Eleventy, Astro and Jekyll output plain HTML with no runtime platform to detect. Some emit a generator tag; many do not.

A headless setup. The content lives in a CMS you cannot see — Contentful, Sanity, Strapi — while the front end is Next.js or similar. The CMS leaves no fingerprint because it never touches the browser.

A custom application. Rails, Laravel, Django. There is no CMS because it is not a CMS.

Deliberate obfuscation. Plugins exist specifically to strip WordPress fingerprints — renaming /wp-content/, removing the generator tag, masking REST endpoints. They are moderately effective against casual inspection.

Absence of evidence is genuinely absence of evidence here. "No CMS detected" is a real and common answer, not a failure.

Step 4: The checks that survive obfuscation

If you suspect a site is hiding its platform, these are harder to mask:

robots.txt. Visit /robots.txt. Default disallow rules are platform-shaped — /wp-admin/ for WordPress, /administrator/ for Joomla, /checkout and /cart for Shopify. People forget to edit this file.

Login paths. /wp-login.php, /administrator/, /user/login, /admin/login. A 200 response is informative. (Check, do not attempt to log in.)

RSS feed structure. /feed/ for WordPress, /rss/ for Ghost. The generator element inside the feed XML often survives when the HTML tag has been stripped.

Response headers. X-Powered-By, X-Generator, and platform-specific headers like X-Shopify-Stage. Run curl -sI https://example.com and read them.

Favicon and asset paths. Default asset locations are rarely changed even when the obvious markers are.

A worked example

Take a site and search the source for /wp-content/. Twenty hits. Now look at one:

<link rel="stylesheet" href="/wp-content/themes/kadence/style.css?ver=1.2.4" />

WordPress, running Kadence, version 1.2.4. Then search /wp-content/plugins/ and you will see the plugin directory names — woocommerce, elementor, wordfence — giving you a reasonable picture of the stack in under a minute.

That is genuinely all there is to it for WordPress. Other platforms give up less, and some give up almost nothing, which is worth knowing before you spend an hour looking for information that was never published.

When to use a tool instead

Manual inspection is fast for one site and tedious for twenty. It also misses things that require fetching additional files — reading a theme's style.css header, following an asset host, checking a source map.

Our detector runs every check above across nine platforms and reports what it found and how confident it is — including telling you when a platform is confirmed but its theme genuinely cannot be read. That distinction matters more than it sounds, because the alternative is a tool that invents a plausible name rather than admitting the limit.