User Agent Parser_
The fields people parse a user agent string for are the fields that have been frozen. Windows 11 reports itself as Windows NT 10.0, exactly like Windows 10. Every Mac says 10.15.7. Every Android phone since Chrome 110 says its model is K. The parsers that print a tidy table of Browser, Version, OS and Device are not wrong about the string — the string stopped being true.
So this one marks each row with how far it can be trusted, and says why in the row itself. Load your own string with one press, or paste one from a log.
What your browser reports instead
- Input
- Any user agent string — pasted from a log, typed by hand, or loaded from your own browser with one press.
- Output
- A field per row with a trust level: real, frozen at a constant, ambiguous between two answers, or absent from the string entirely.
- Processing
- Parsed in this tab. Your own string comes from navigator.userAgent, and the Client Hints panel reads navigator.userAgentData where the browser provides it.
- Limits
- Everything here is self-reported by whatever sent the request. A string is a claim, not evidence, and can be changed in a browser setting or a single header.
- The one number that is never what it looks like
- Safari's version is in the Version/ token, never in Safari/. That second number is the WebKit build — it has read 605.1.15 on macOS and 537.36 on Chromium for years, and parsers that read it report every Safari user as Safari 605.
What the string stopped telling you
Windows 11 is invisible
Microsoft never incremented the NT version. Windows 10 and Windows 11 both send Windows NT 10.0, and there is no other token that separates them. Any dashboard showing a Windows 10 versus Windows 11 split is getting it from Client Hints or from guesswork, and a great many are quietly reporting a hundred percent of their Windows traffic as Windows 10 years after the successor shipped.
Every Android device is called K
Chrome's User-Agent Reduction replaced the device model with the literal letter K and pinned the Android version to 10 for every phone, whatever it is actually running. This is the most visible of the changes because it turns a long tail of device models in an analytics report into one enormous bucket, and because K looks like a parsing bug rather than the intended value. It is the intended value.
macOS has said 10.15.7 since Catalina
Safari and Chrome both freeze it. A Mac running the current release reports the version number of an operating system from 2019, and it will keep doing so. The value still tells you the machine is a Mac, which is worth something; it tells you nothing at all about what it runs.
An iPad usually says it is a Mac
Request Desktop Website has been Safari's default on iPad since iPadOS 13, and in that mode the tablet sends a string that is byte-for-byte a Mac's. There is no iPad token, no Mobile token, nothing. Touch support in Client Hints is the only clean way to tell them apart, which means tablet traffic in most reports is smaller than it is and Mac traffic is larger — a systematic error, not a rounding one.
Chromium version numbers end in zeros
Since Chrome 110 the minor, build and patch components are all reported as 0.0.0. The major version is real and everything after it is padding, so a bug report that pins a fault to a specific Chrome build cannot get that build from the string — only from navigator.userAgentData.getHighEntropyValues, and only if you ask for it.
Everything before the browser name is a lie by design
Every browser opens with Mozilla/5.0, a claim Internet Explorer started making in 1996 to get past servers that checked for Netscape. Chrome runs Blink and still says AppleWebKit and Safari, because sites broke when it stopped. Edge says Chrome. Each token was added to defeat someone's detection code, and none has ever been removed, which is why the order a parser checks its patterns in matters more than the patterns themselves — test for Chrome first and Edge, Opera, Vivaldi, Brave and Samsung Internet all disappear into it.
Read a string, or read your own
- 01Press Yours to load the string your browser is sending right now, or paste one from a server log.
- 02Read the trust marker beside each row before the value. A frozen field is a constant, not a measurement.
- 03Check the panel at the bottom for what your browser exposes through Client Hints — that is where the values the string lost have gone.
- 04Try the samples when a string in a log looks wrong. Most of the surprising ones are on that row.
A bug report you cannot reproduce
The reporter's string names a browser and a major version, and nothing more precise than that. Asking for the exact build is the only way forward.
Chrome/138.0.0.0
Chrome 138 — some build of it. The exact one is only in getHighEntropyValues.
Analytics showing one device model
Half the Android traffic is attributed to a device called K, and it looks like the pipeline is broken. It is not.
Device: K — 61%
Chrome 110 replaced every Android model with that letter. There is no model in the string any more.
Tablet numbers that look too low
iPads have sent a desktop Mac string by default since iPadOS 13, so they land in the macOS bucket instead of the tablet one.
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) …
A Mac, or an iPad. Only Client Hints can tell you which.
Deciding whether a crawler is really the crawler
A request claims to be Googlebot. The token costs nothing to copy, so the claim is worth exactly nothing on its own.
… Googlebot/2.1; +http://www. google.com/bot.html)
Reverse DNS the source IP and forward-confirm it. Google, Bing, OpenAI and Anthropic all publish a way to check.
Frozen, ambiguous and real
| Field | What you get | Status |
|---|---|---|
| Windows release | Windows NT 10.0 | Ambiguous — Windows 10 and 11 are identical here, and always will be. |
| macOS version | 10_15_7 | Frozen since Catalina. Every Mac reports it. |
| Android device | K | Frozen in Chrome 110. One placeholder for every phone. |
| Android version | 10 | Frozen alongside the device, on Chromium browsers. |
| Chromium version | 138.0.0.0 | Major only. The last three components are zeroed. |
| iPad | Macintosh | Ambiguous — desktop mode is the default, and it hides the tablet. |
| iOS version | 18_5 | Real. The one mainstream platform still reporting its version. |
| Browser name | Edg/, OPR/, CriOS/ | Real, if you test for it before Chrome. Every one of these strings also contains Chrome. |
| Safari version | Version/18.5 | Real — but not the number in Safari/605.1.15, which is the WebKit build. |
Nothing in this table is a bug in a parser. Each row is a value a vendor deliberately stopped updating, and a tool that prints it without saying so is passing on a constant as a measurement.
Working with user agent strings
- Test for the specific browsers before the general ones. Edge, Opera, Vivaldi, Brave and Samsung Internet all carry the Chrome token, and checking for Chrome first silently merges them into it.
- Detect features rather than browsers wherever you can. A capability check answers the question you actually have, and it does not need a new rule every time a vendor changes a token.
- Never treat the string as authentication. It is set by the client, changeable in a menu, and the first thing any scraper edits.
- Verify crawler claims by IP, not by token. Google, Bing, OpenAI, Anthropic and the serious SEO crawlers all publish either reverse DNS or an address range.
- Move anything that matters to Client Hints before the string loses more. The low-entropy hints arrive on every request; the useful ones have to be requested, and on the server that means an Accept-CH header.
Where user agent parsing goes wrong
A confident table is the failure mode
The risk is not a parser that fails — it is one that prints Windows 10 for a Windows 11 machine and Android 10 for a phone running something else. Both look like data, and neither is.
Checking for Chrome first merges five browsers into one
Edge, Opera, Vivaldi, Brave and Samsung Internet all contain the Chrome token. This is the single most common bug in hand-written detection, and it shows up as a Chrome share that is several points too high rather than as an error.
The string is a claim and nothing else
Anyone can send anything. Blocking or granting on the user agent stops well-behaved clients and no one else, which is why it belongs in analytics and never in an access control decision.
Parsing it for fingerprinting is a losing position
Reduction exists precisely to remove passive entropy from the header. The values are being taken away on a schedule, and anything built on them needs rebuilding each time — which is the point.
Format, reduction and Client Hints
- Header
- User-Agent, defined in RFC 9110 as a sequence of product tokens with optional comments. The format permits almost anything, which is why parsing it is pattern matching rather than parsing.
- Reduction
- Chrome froze the minor version in 110, the desktop platform version, and the Android device model and OS version. Firefox and Safari cap or freeze several of the same values.
- Client Hints
- Sec-CH-UA, Sec-CH-UA-Mobile and Sec-CH-UA-Platform are sent by default on Chromium. Platform version, device model, full browser version and architecture are high entropy and must be requested with Accept-CH, or read in script through navigator.userAgentData.getHighEntropyValues.
- Availability
- navigator.userAgentData is Chromium only. Firefox and Safari have not shipped it, so a fallback to the string is still required and this page shows what your own browser exposes.
- Detection order
- Specific tokens before general ones: EdgiOS, CriOS and FxiOS before Safari; Edg, OPR, SamsungBrowser, Vivaldi and YaBrowser before Chrome; Chrome before Safari. The test suite here asserts each of those.
- Network
- None from tool code. A test sweep calls every function this page uses with
fetchandXMLHttpRequestreplaced by stubs that throw, so a stray request fails the build instead of shipping. Disconnect from the network and the page still works.
Questions about user agent strings
What is a user agent string?
A header the client sends with every request, naming the browser, its rendering engine and the platform it is running on. It is free-form text chosen by the client, which is why it can be edited, spoofed, or simply wrong.
How do I find my own user agent?
Press Yours above and it loads from navigator.userAgent, then parses it. In a browser console the same value is available by typing navigator.userAgent, and any server sees it on every request you make.
Why does my user agent say Mozilla when I use Chrome?
Because Internet Explorer claimed to be Mozilla in 1996 to get served the good version of pages that checked for Netscape, and every browser since has kept the token to avoid the same problem. It carries no information now and no parser reads it.
Why does Chrome say it is Safari?
Chrome ran WebKit until 2013 and forked it into Blink. It kept both the AppleWebKit and Safari tokens because sites were serving degraded pages to anything without them. Edge then kept the Chrome token for the same reason, so an Edge string names three browsers it is not.
Can a user agent tell Windows 10 from Windows 11?
No. Both report Windows NT 10.0 and there is nothing else in the string to separate them. The only reliable source is the high-entropy Client Hint for platform version, which the server has to ask for with an Accept-CH header.
Why does my Android device show up as K?
Chrome 110 replaced the device model with that placeholder as part of User-Agent Reduction, and pinned the Android version to 10 at the same time. It is not a parsing failure — the model is genuinely no longer in the string, and getHighEntropyValues is where it went.
Why is an iPad detected as a Mac?
Safari on iPad defaults to Request Desktop Website, and in that mode it sends the same Macintosh string a Mac does. Nothing distinguishes them, which means tablet traffic is undercounted and Mac traffic overcounted almost everywhere.
Is a user agent string reliable?
As a hint, usually. As a fact, never. The client chooses it, browsers freeze parts of it, extensions rewrite it, and privacy modes randomise it. Use it for aggregate reporting and use feature detection for anything that changes what your code does.
What are User-Agent Client Hints?
A set of Sec-CH-UA headers that replace the string with structured, requestable values. The low-entropy ones — brand list, mobile flag, platform — are sent automatically on Chromium; anything more detailed must be asked for explicitly. Firefox and Safari have not implemented them.
How do I tell a real crawler from a fake one?
Not from the string. Take the source IP, do a reverse DNS lookup, then forward-resolve the result and check it matches. Google, Bing, OpenAI, Anthropic, Ahrefs and Semrush all document this or publish an address range.
Is the string I paste here sent anywhere?
No. The work is JavaScript running in this tab. Every function it calls is covered by a test that stubs fetch and XMLHttpRequest to throw, so a request that slipped in would break the build rather than reach a server — and you can confirm it for yourself by disconnecting and carrying on.