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.

toolkit.codes/user-agent-parser
Try

What your browser reports instead

UTF-8
Ready
100% LOCAL
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

  1. 01Press Yours to load the string your browser is sending right now, or paste one from a server log.
  2. 02Read the trust marker beside each row before the value. A frozen field is a constant, not a measurement.
  3. 03Check the panel at the bottom for what your browser exposes through Client Hints — that is where the values the string lost have gone.
  4. 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.

What the string gives you
Chrome/138.0.0.0
What it means
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.

In the report
Device: K — 61%
What happened
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.

What arrives
Mozilla/5.0 (Macintosh; Intel
Mac OS X 10_15_7) …
What it might be
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.

Claimed
… Googlebot/2.1; +http://www.
google.com/bot.html)
What to do
Reverse DNS the source IP and
forward-confirm it. Google, Bing,
OpenAI and Anthropic all publish
a way to check.

Frozen, ambiguous and real

FieldWhat you getStatus
Windows releaseWindows NT 10.0Ambiguous — Windows 10 and 11 are identical here, and always will be.
macOS version10_15_7Frozen since Catalina. Every Mac reports it.
Android deviceKFrozen in Chrome 110. One placeholder for every phone.
Android version10Frozen alongside the device, on Chromium browsers.
Chromium version138.0.0.0Major only. The last three components are zeroed.
iPadMacintoshAmbiguous — desktop mode is the default, and it hides the tablet.
iOS version18_5Real. The one mainstream platform still reporting its version.
Browser nameEdg/, OPR/, CriOS/Real, if you test for it before Chrome. Every one of these strings also contains Chrome.
Safari versionVersion/18.5Real — 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 fetch and XMLHttpRequest replaced 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.