User-Agent strings are a uniquely accreted piece of web history: nearly every browser's UA string starts with 'Mozilla/5.0' for backward-compatibility reasons dating back to the 1990s browser wars, even though almost no browser alive today is built on Mozilla's original codebase. Reading a modern UA string means understanding which tokens are load-bearing (Chrome/120.0.0.0 actually tells you the browser and version) and which are vestigial compatibility signaling that every browser includes regardless of relevance (like AppleWebKit and Gecko in a Chrome UA string, even though Chrome uses Blink, a WebKit fork, not Gecko).
The specific irony is that Chrome's UA string contains 'like Gecko' and 'Safari' tokens purely so that older websites doing naive substring checks for those browsers don't break — Chrome spent years imitating Safari's UA format because so much of the web checked for 'Safari' as a proxy for 'supports modern CSS,' and removing those tokens would have broken sites that never updated their detection logic. This is exactly why user-agent sniffing is considered fragile: the string tells you what a browser wants you to infer, filtered through two decades of workarounds for other people's bad detection logic, not a clean machine-readable declaration.
Modern browsers are actively reducing how much real information the UA string carries at all — Chrome's User-Agent Reduction effort freezes and generalizes major version-specific details, pushing sites toward the more deliberate User-Agent Client Hints API for anything that actually needs accurate version or platform data. A UA string today is best treated as a rough, spoofable hint, not a reliable source of truth for anything security-relevant like access control.
This parser identifies the actual browser and version by checking tokens in the order that correctly disambiguates Chrome from Edge from Safari from the many Chromium-based browsers that all include a 'Chrome/' token, extracts the rendering engine and OS, and calls out which tokens in the string are legacy compatibility noise versus the parts that carry real signal.
Edge cases worth knowing
- A Chromium-based browser that isn't Chrome
- Edge, Brave, Opera, and Vivaldi all include a Chrome/ token in their UA string because they're built on Chromium, plus their own distinguishing token later in the string (Edg/, OPR/, and similar). Checking only for 'Chrome' misidentifies all of them — this parser checks the more specific, later tokens first.
- A mobile browser's UA string on a device that's actually a tablet
- Some Android tablets include 'Mobile' in their UA string despite having tablet-class screens, and some don't — the Mobile token is a rough hint about the device class the browser wants to present, not a reliable screen-size indicator. This parser reports what the string claims without assuming it's accurate for layout decisions.
- A UA string that's been deliberately overridden or spoofed
- Browser DevTools and many extensions can freely override the UA string sent, and this is completely undetectable from the string itself. Any UA-based logic in a security-relevant path (rather than a cosmetic one, like showing an app-store badge) should be treated as advisory only.
Common mistakes
- Using UA string sniffing to gate access to a feature or API, when it can be trivially spoofed by anyone with browser DevTools open.
- Checking for the substring 'Chrome' and inadvertently matching Edge, Opera, Brave, and every other Chromium-based browser that includes the same token.
- Assuming the version number in a UA string reflects the browser's actual capabilities, when User-Agent Reduction efforts increasingly freeze or generalize that number.
- Building critical layout or compatibility logic around UA parsing instead of feature-detecting the specific API or CSS property actually needed.