User-Agent Strings, Decoded

After reading this you will be able to take any user-agent string apart into browser, engine, operating system and device, and know why almost every browser starts its identity with the word Mozilla.

What a user-agent string is

Every HTTP request your browser sends carries a User-Agent header. It is a single line of text that names the software making the request. Servers read it, log it, and sometimes change what they send back based on it.

Here is a real example, the kind of line the parser decodes:

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

Read literally, that string claims to be four different things at once: Mozilla, AppleWebKit, Chrome, and Safari. It is Chrome. The other three names are historical camouflage. A parser exists precisely because you cannot read this line at face value.

The header itself is defined in RFC 9110, section 10.1.5. The spec says the field lists product tokens with optional comments, and it explicitly warns that overly detailed user-agent values make requests easier to fingerprint. The chaos inside real strings is convention, not standard.

Why every browser claims to be Mozilla

In 1994 Netscape Navigator sent Mozilla/2.0. Servers learned to send frames and rich HTML only to clients whose UA started with Mozilla. When Internet Explorer arrived, it wanted those same pages, so it also sent Mozilla, then added compatible; MSIE in a comment. Every browser since has copied the trick to avoid being served a degraded page.

The pattern repeated at each layer. When WebKit browsers wanted pages written for Safari, they added Safari. When Chrome shipped on top of WebKit, it kept AppleWebKit and Safari and appended Chrome. When Edge moved to Chromium, it kept all of that and added Edg. The string grew by accretion. Nobody removed the old names because some server, somewhere, still sniffs for them.

The word like Gecko in the comment is another lie of convenience. Gecko is Firefox's engine, not WebKit's. Chrome includes the phrase so that scripts checking for Gecko-era behavior do not reject it.

The four things a parser extracts

Parsing is not arithmetic, so there is no single formula. The work is pattern matching against known token orders. A good parser resolves four independent facts:

Browser and version
The last meaningful product token usually wins. Chrome/120 beats the trailing Safari/537.36 because Chrome appends its name after WebKit's.
Rendering engine
The layout engine and its version: AppleWebKit (Blink and Safari), Gecko (Firefox), or older Trident (Internet Explorer).
Operating system
Read from the parenthesized comment: Windows NT 10.0, Macintosh, Android 14, iPhone OS 17_2.
Device class
Inferred, not stated. The presence of Mobile, iPhone, or Android plus Mobile marks a phone. Its absence usually means desktop.

The order of precedence matters. Because Chrome, Edge and Opera all contain Chrome, a parser must check for the more specific token first. If you test for Chrome before Edg, you will label every Edge user as Chrome.

When to use UA parsing, and when not to

Parsing is the right tool when you are reading data after the fact: analyzing server access logs, segmenting past traffic, or reproducing a bug a user reported. Here the string is all you have, and rough classification is enough.

Parsing is the wrong tool when you are deciding, live, what code to run. Two problems make that fragile. First, strings lie by design, so your logic inherits every historical hack. Second, modern Chrome now freezes most of the string. Since Chrome 101 the minor version digits are reported as 0.0.0, so 120.0.6099.109 arrives as 120.0.0.0. The device model on Android is reduced to a generic K.

Do not gate features on the parsed browser name in new code. Use feature detection (if ('IntersectionObserver' in window)) or the Client Hints headers (Sec-CH-UA, Sec-CH-UA-Platform). The UA string is kept only so old sniffing scripts do not crash.

A worked example: the pre-filled string

Decoding the demo Chrome-on-Windows line

Take the sample the tool pre-fills:

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

  1. Strip the leading Mozilla/5.0. Every mainstream browser sends it, so it carries zero information. Discard.
  2. Read the first comment: (Windows NT 10.0; Win64; x64). The token Windows NT 10.0 maps to Windows 10 or 11 (they share the same NT version). Win64; x64 means a 64-bit build. Operating system resolved.
  3. Find the engine: AppleWebKit/537.36. This is a WebKit or Blink engine at build 537.36. The (KHTML, like Gecko) comment is decorative and ignored.
  4. Scan for browser tokens in order of specificity. There is no Edg, OPR, or SamsungBrowser. There is Chrome/120.0.0.0. Browser is Chrome, version 120.
  5. The trailing Safari/537.36 is the compatibility tail. Because Chrome already matched, do not overwrite the result. Ignore it.
  6. Check for device markers: no Mobile, no Android, no iPhone. Device class is desktop.

Final reading: Chrome 120, Blink or WebKit engine build 537.36, Windows 10 or 11, 64-bit, desktop.

How token specificity drives the result

The single most instructive thing about parsing is that the same substring belongs to several browsers, so order decides the answer. The chart below shows how many mainstream desktop browser strings contain each token. Notice that Chrome appears in four of them, which is why it must be the last thing you test for, not the first.

Chrome, Edge, Opera and Chrome itself all carry Chrome, so a specific token like Edg must be matched before the generic Chrome.

The sample set here is Firefox, Safari, Chrome, Edge, Opera and Chrome on Android. Mozilla scores 6 because all six send it. Edg and Firefox score 1 because they are unique markers. A parser reads uniqueness in reverse: the rarer the token, the more it tells you.

Given the string Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120 Edg/120 Safari/537.36, checking tokens in the wrong order matters: testing Chrome before Edg reports Chrome, but this line is Edge. Test Edg first and you get Edge.

Common mistakes when reading results

Four errors account for most misreadings.

  • Trusting the trailing Safari token. Almost every Blink browser ends with Safari/537.36. On its own it does not mean Safari. Real Safari has Version/17.2 and no Chrome token.
  • Confusing engine version with browser version. AppleWebKit/537.36 has been frozen at 537.36 for years. It is not the browser version. The browser version lives in Chrome/120.
  • Mapping Windows NT wrongly. Windows 11 still reports Windows NT 10.0. The string cannot distinguish Windows 10 from 11. Any tool that claims to is guessing.
  • Reading frozen digits as precision. A version of 120.0.0.0 does not mean patch level zero. Chrome reports zeros deliberately. The true patch number is hidden unless the site requests high-entropy client hints.

Related tools

Once you have a request decoded, these help with the rest of the exchange. The URL Parser and Query Editor splits the request target into parts. The HTTP Status Codes reference explains the response code the server returned. The MIME Type Lookup decodes the Content-Type header. For front-end work that replaces UA sniffing, the Keycode Inspector shows the same feature-detection mindset applied to keyboard events.

Frequently asked questions

Why does Chrome say it is Safari and Mozilla?

For compatibility. Servers historically sent good pages only to strings that named Mozilla, and later only to strings that named Safari for WebKit content. Chrome keeps both so no legacy sniffing script serves it a broken page.

Can I trust the operating system from a user-agent?

Roughly. You can tell Windows from macOS from Android. You cannot tell Windows 10 from Windows 11, because both report Windows NT 10.0. On iOS every browser reports the Safari engine, so you cannot tell Chrome-on-iPhone from Safari from the UA alone.

Is the user-agent string being removed?

Not removed, but frozen. Chrome now sends fixed values for most fields (120.0.0.0 instead of the full version, K instead of the device model). The detailed data moves into User-Agent Client Hints, which a site must explicitly request.

Can a user-agent be faked?

Yes, trivially. It is a plain header the client sets. Browser extensions and command-line tools change it in one line. Never use it for security decisions or to trust that a request came from a real browser.

What is the difference between the engine and the browser?

The engine renders HTML and CSS (Blink, WebKit, Gecko). The browser is the application wrapped around it (Chrome, Safari, Firefox, Edge). Chrome and Edge both use Blink, so they render identically but ship different features and UIs.

The parser runs entirely in your browser. Nothing you paste is sent anywhere, so you can safely test the exact string from a private server log.