DevToys Web Pro iconDevToys Web ProBlogu
Imetafsiriwa kwa LocalePack logoLocalePack
Tupatie ukadiriaji:
Jaribu kiendelezi cha kivinjari:
← Back to Blog

User Agent Parsing Guide: UA Structure, Client Hints, and Bot Detection

9 min read

Every HTTP request carries a User-Agent header — a string that is supposed to identify the client making the request. In practice, it is one of the most misunderstood and misused pieces of data in web development. Parse it with the User Agent Parser to follow along with the examples below.

Anatomy of a Typical User Agent String

Take this UA from Chrome 122 on Windows:

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36

Breaking it down token by token:

TokenMeaning
Mozilla/5.0Legacy compatibility prefix — present in virtually every modern browser
(Windows NT 10.0; Win64; x64)OS and architecture — Windows 10, 64-bit
AppleWebKit/537.36Rendering engine — Blink (Chrome's fork of WebKit) reports this
(KHTML, like Gecko)Another compatibility token; KHTML was an early open-source layout engine
Chrome/122.0.0.0Actual browser and major version
Safari/537.36Trailing Safari token — present in all Chromium-based browsers

Notice that the string mentions Mozilla, WebKit, KHTML, Gecko, and Safari — yet the browser is Chrome. This is not a bug. It is the product of twenty-five years of incremental compatibility theater.

Historical Baggage: Why Every Browser Lies

The UA string mess started in the mid-1990s. Netscape Navigator called itself Mozilla/1.0. When Internet Explorer launched, some servers only delivered rich content to Mozilla/* agents, so IE identified itself as Mozilla/2.0 (compatible; MSIE 3.0) to get through the gate. Then sites started gating on Gecko, so browsers added like Gecko tokens. Then on AppleWebKit, so Chromium added that. Then on Safari, so Chromium added that too.

Each round of UA sniffing by web servers drove browsers to impersonate more engines. Today, every Chromium-based browser — Chrome, Edge, Opera, Brave, Arc — sends a UA string that claims to be Mozilla, WebKit, KHTML, Gecko, and Safari simultaneously. Firefox reports Gecko accurately but still leads with Mozilla/5.0.

The practical result: you cannot reliably parse the UA string to determine the real browser engine without a dedicated, regularly updated parser library. And even then, you are parsing a string designed to deceive.

The UA Freezing and Reduction Initiative

Browser vendors recognized that detailed UA strings are a fingerprinting vector — they help third parties track users across sites even without cookies. Starting around 2020, Chrome announced a phased UA reduction plan:

  • Minor version numbers are frozen to 0.0.0 — you see Chrome/122.0.0.0, not Chrome/122.0.6261.112.
  • The OS version in the UA is reduced to a small set of representative values (e.g., Windows always reports Windows NT 10.0 regardless of the actual build).
  • On mobile, device model strings are being phased out in favor of a generic Android token.
  • Firefox and Safari have implemented similar entropy-reduction policies.

The intent is to make all User-Agent strings in a browser family look identical, eliminating their value as a fingerprinting signal — and as a source of reliable version data.

Client Hints: The Modern Alternative

To fill the gap left by UA reduction, the W3C standardized User-Agent Client Hints (UA-CH). Instead of embedding everything in one opaque header, the browser exposes structured data through separate headers — but only when the server explicitly requests them.

The server opts in by returning an Accept-CH response header:

Accept-CH: Sec-CH-UA, Sec-CH-UA-Platform, Sec-CH-UA-Mobile, Sec-CH-UA-Full-Version-List

On subsequent requests the browser sends the requested hints:

Sec-CH-UA: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"
Sec-CH-UA-Platform: "Windows"
Sec-CH-UA-Mobile: ?0
Sec-CH-UA-Full-Version-List: "Chromium";v="122.0.6261.112", "Google Chrome";v="122.0.6261.112"

The key Client Hints headers:

HeaderDataNotes
Sec-CH-UABrand list + major versionSent by default on HTTPS
Sec-CH-UA-MobileBoolean — is the device mobile?Sent by default on HTTPS
Sec-CH-UA-PlatformOS name ("Windows", "macOS", "Android")Sent by default on HTTPS
Sec-CH-UA-Full-Version-ListBrand list + full version stringRequires explicit Accept-CH
Sec-CH-UA-Platform-VersionOS versionRequires explicit Accept-CH
Sec-CH-UA-ArchCPU architectureRequires explicit Accept-CH

Client Hints only work over HTTPS, only in Chromium-based browsers (Firefox and Safari do not implement them yet), and require the server to request them. They are more structured and privacy-preserving than the classic UA string, but they are not yet universal.

JavaScript: navigator.userAgentData vs navigator.userAgent

The same data is available in JavaScript. The legacy API returns the raw string:

// Legacy — returns the full opaque string
console.log(navigator.userAgent);
// "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..."

The modern API returns structured data, but is only available in Chromium browsers:

// Modern — structured, available in Chromium only
const uaData = navigator.userAgentData;

console.log(uaData.brands);
// [{ brand: "Chromium", version: "122" }, { brand: "Google Chrome", version: "122" }, ...]

console.log(uaData.mobile);   // false
console.log(uaData.platform); // "Windows"

// High-entropy values require a promise-based call
const high = await uaData.getHighEntropyValues([
  "architecture",
  "platformVersion",
  "fullVersionList",
]);
console.log(high.platformVersion); // "15.0.0"

For cross-browser code, always feature-detect before using navigator.userAgentData:

function isMobile() {
  if (navigator.userAgentData) {
    return navigator.userAgentData.mobile;
  }
  // Fallback to UA string parsing for Firefox / Safari
  return /Mobi|Android/i.test(navigator.userAgent);
}

When UA Parsing Is Still Legitimate

Despite its limitations, UA parsing remains valid in specific scenarios:

  • Analytics and reporting: Aggregate traffic breakdowns by browser family, OS, and device type are still useful even with reduced fidelity. Exact version numbers are rarely needed for dashboards.
  • Bot and crawler detection: Legitimate crawlers identify themselves with recognizable UAs. Combining UA checks with behavioral signals (request rate, missing headers, JavaScript execution) is a practical first-pass filter.
  • Feature-detection fallback: When a CSS feature query or JS capability check is not available, a UA-based heuristic can serve as a last resort — but prefer @supports and feature detection over UA sniffing for this use case.
  • Serving different asset bundles: Delivering a legacy JS bundle to old browsers and a modern bundle to current ones. UA-based routing is coarse but fast.
  • Debugging and support tooling: Logging the UA alongside error reports helps reproduce issues on specific browser/OS combinations.

Common Bot and Crawler User Agents

BotUser-Agent TokenOwner
GooglebotGooglebot/2.1Google Search
Google AdsBotAdsBot-GoogleGoogle Ads quality check
Bingbotbingbot/2.0Microsoft Bing
ChatGPT-UserChatGPT-UserOpenAI (browsing plugin)
GPTBotGPTBot/1.0OpenAI (training crawler)
Claude-WebClaude-Web/1.0Anthropic (browsing)
ClaudeBotClaudeBot/0.5Anthropic (training crawler)
AhrefsBotAhrefsBot/7.0Ahrefs SEO
SemrushBotSemrushBot/7~blSemrush SEO
DotBotDotBot/1.2Moz
facebookexternalhitfacebookexternalhit/1.1Facebook link preview
TwitterbotTwitterbot/1.0Twitter/X card preview

Legitimate crawlers from Google and Bing can be verified by reverse DNS lookup — check that the IP address of the requester resolves to a hostname in googlebot.com or search.msn.com and that the forward lookup matches. UA strings alone can be trivially spoofed.

Parsing Libraries

If you need to parse UA strings programmatically, use a maintained library rather than hand-rolling regex patterns.

# JavaScript
npm install ua-parser-js
import { UAParser } from 'ua-parser-js';

const parser = new UAParser(request.headers['user-agent']);
const result = parser.getResult();

console.log(result.browser.name);    // "Chrome"
console.log(result.browser.version); // "122.0.0.0"
console.log(result.os.name);         // "Windows"
console.log(result.device.type);     // undefined (desktop) | "mobile" | "tablet"
# Python
pip install user-agents
from user_agents import parse

ua_string = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..."
ua = parse(ua_string)

print(ua.browser.family)   # "Chrome"
print(ua.os.family)        # "Windows"
print(ua.is_mobile)        # False
print(ua.is_bot)           # False
# Go
go get github.com/mssola/useragent
import "github.com/mssola/useragent"

ua := useragent.New("Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...")

name, version := ua.Browser()
fmt.Println(name)        // "Chrome"
fmt.Println(version)     // "122.0.0.0"
fmt.Println(ua.OS())     // "Windows"
fmt.Println(ua.Mobile()) // false

All three libraries share the same trade-off: they rely on maintained signature databases that must be updated as browsers release new versions. Pin your dependency and update regularly, or you will start misidentifying new browser releases as unknown.

Pitfalls and What Not to Do

  • Do not use UA for access control. Any client can send any UA string. Using it to gate premium features or security-sensitive routes is trivially bypassed with curl -H "User-Agent: ...".
  • Do not assume UA equals identity. Browser extensions frequently modify the UA string. Corporate proxies sometimes rewrite it. Testing tools like Playwright and Puppeteer default to a headless UA but can be configured to send anything.
  • Headless browser detection is an arms race. Checking for HeadlessChrome in the UA used to catch automation tools. Modern headless Chrome no longer includes that token. Behavioral signals — mouse movement entropy, timing patterns, WebGL fingerprints — are more reliable than UA checks for anti-bot work.
  • Do not parse UA to infer CSS feature support. Use @supports, CSS.supports(), and progressive enhancement instead. A UA-based feature matrix goes stale immediately and is wrong for Edge cases (pun intended).
  • UA reduction will make version strings less useful over time. Build server-side logic against Client Hints for Chromium users and accept reduced fidelity for Firefox and Safari.

Inspect and parse any user agent string in your browser with the User Agent Parser. For more testing tools, see the Testers Guide.