Skip to main content
snaptools

Guide

How to read a User-Agent string

Read a User-Agent string from a log line, work out which tokens are frozen on purpose, and use Client Hints or feature detection for what the string can no longer tell you.

SnapTools Editorial ·
Try it — User Agent Parser
UA
User Agent Parser
Browser
Engine
OS
Device
CPU
Paste a User-Agent string to break it into parts

To read a User-Agent string, skip the first word, read the tokens from the right, and ask of every value whether it can still be true. The browser name and its major version usually can. The operating system version, the device model, and anything about Windows 11, a current Mac or iOS 26 usually cannot. Paste a string into the box above to follow along.

Start from the log line

This is a line from an nginx access log in the default combined format. The address is from a range reserved for documentation.

203.0.113.7 - - [24/Oct/2026:09:14:02 +0000] "GET /pricing HTTP/2.0" 200 5120 "https://example.com/" "Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1"

The User-Agent is the last quoted field. To count the most common ones in a file, split on the double quote and take the sixth field:

awk -F'"' '{print $6}' access.log | sort | uniq -c | sort -rn | head

On a four-line sample file this printed the iPhone string first, with a count of 2.

Read it in five steps

  1. Ignore the first word. Mozilla/5.0 is not a version. MDN says almost every browser still sends it for historical reasons.
  2. Read the tokens from the right. The most specific name sits near the end. A string that ends Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0 is Microsoft Edge, because Edge adds Edg after the Chrome tokens. Opera adds OPR. Safari puts Version/ before Safari/, which Chromium browsers do not.
  3. Open the parentheses. They hold the platform: Windows NT 10.0; Win64; x64, Macintosh; Intel Mac OS X 10_15_7, X11; Linux x86_64, Linux; Android 10; K, iPhone; CPU iPhone OS 18_6 like Mac OS X.
  4. Look for Mobile. MDN's rule of thumb is to look for Mobi anywhere in the string. A phone on Chrome has Mobile before Safari/. A tablet does not.
  5. Check every value against the frozen list below.

Walk through the iPhone line

  • Mozilla/5.0 is the skipped word.
  • (iPhone; CPU iPhone OS 18_6 like Mac OS X) is an iPhone. It claims iOS 18.6. The words like Mac OS X are a legacy claim.
  • AppleWebKit/605.1.15 names the WebKit engine.
  • Version/26.0 is Safari 26.0.
  • Mobile/15E148 confirms a phone. Ignore the build code.
  • Safari/604.1 is a trailing compatibility token.

So this is Safari 26 on an iPhone. The OS number needs care. The parser above prints iOS 26.0 for it, and it gets there by copying the Safari version into the OS field, because Safari 26 no longer sends the real iOS version. The next section says why.

Which values are frozen on purpose

  • Chrome and Edge on desktop send Windows NT 10.0 for Windows 10 and for Windows 11, Intel Mac OS X 10_15_7 on a Mac and X11; Linux x86_64 on Linux. The version ends in .0.0.0. Chrome froze the minor version from Chrome 101 and reduced desktop platform tokens from Chrome 107.
  • Chrome and Edge on Android send Android 10; K, the same for every phone. Chrome reduced these from Chrome 110. K is a placeholder where the model used to be.
  • Safari on a Mac says 10_15_7 whatever the macOS release. An iPad in its default mode sends the same Macintosh string, so it looks like a Mac.
  • Safari 26 on iOS says 18_6. The WebKit release notes of 15 September 2025 say the string no longer lists the current OS version, and a WebKit developer called the freeze permanent. Chrome on iOS still reports the real version.
  • Firefox fixes Gecko/20100101 on desktop. From Firefox 110 it held rv: at 109 because some sites read rv:11 as Internet Explorer.

The practical rule: browser family, engine family and major browser version are usable. The OS version is not. The device model is not. If your analytics shows an OS version column built from the User-Agent, every Windows 11 visitor is in the Windows 10 row.

Use Client Hints for the rest

Chromium browsers send three hints with every HTTPS request: Sec-CH-UA (brand and major version), Sec-CH-UA-Mobile and Sec-CH-UA-Platform. Anything finer is only sent after the server asks for it with an Accept-CH response header. The exchange looks like this, using the sample values from Microsoft's documentation:

GET /download HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Edg/120.0.0.0
Sec-CH-UA: "Not_A Brand";v="8", "Chromium";v="120", "Microsoft Edge";v="120"
Sec-CH-UA-Mobile: ?0
Sec-CH-UA-Platform: "Windows"

HTTP/1.1 200 OK
Accept-CH: Sec-CH-UA-Platform-Version

GET /download HTTP/1.1
Sec-CH-UA-Platform-Version: "15.0.0"

Three things to know before you rely on this.

First, the values arrive inside double quotes, so strip them before you compare.

Second, a first number of 13 or higher in Sec-CH-UA-Platform-Version means Windows 11. Microsoft's Windows 11 page and sample code use 13. Its general Edge page says 14.0.0 or later. Use 13, and see the User Agent Parser page for the note.

Third, support is narrow. MDN's compatibility data lists Chrome, Edge and Opera, and shows no support in Firefox or Safari. navigator.userAgentData, the JavaScript side, is also missing from Android WebView in that data. For those clients the User-Agent is all you get.

Bots and crawlers

A crawler usually keeps the Mozilla prefix and adds its name and an info URL in a comment. Google's desktop string contains compatible; Googlebot/2.1; +http://www.google.com/bot.html, followed by a Chrome version Google writes as W.X.Y.Z. OpenAI publishes three tokens with three jobs: GPTBot collects content that may be used for training, OAI-SearchBot feeds ChatGPT search, and ChatGPT-User acts when a person asks. OpenAI says robots.txt rules may not apply to ChatGPT-User. Anthropic's help article names ClaudeBot, Claude-User and Claude-SearchBot but does not print the full header, so match the name and not the whole line.

Not every bot starts with Mozilla. curl/7.64.1 does not. A User-Agent echo page showed one assistant-driven fetch as Claude-User (claude-code/2.1.308; +https://support.anthropic.com/), with no Mozilla prefix at all. In the parser above, curl and that string both come back empty. OpenAI's two published strings and the ClaudeBot string reported by third parties come back with the engine, WebKit 537.36, as the browser, because the tool does not run bot detection.

Treat the string as a claim. A Windows Chrome string with Googlebot/2.1 appended parses as a normal Chrome visit. To confirm a Googlebot visit, Google says to run a reverse DNS lookup on the IP address, check the domain is googlebot.com, google.com or googleusercontent.com, then resolve that name forward and confirm it returns the same address. Google also publishes IP range files for large-scale checks.

When not to use the User-Agent

  • To decide whether a feature exists. MDN calls this the worst reason to sniff. Test the feature itself, or use @supports in CSS.
  • To pick a layout. Use media queries. MDN suggests navigator.maxTouchPoints over a mobile regular expression when you need to know about touch.
  • To make a security decision. The string is whatever the client sends. RFC 9110 itself notes that a client that masquerades as another is treated as asking for responses tailored to that product.
  • To report OS versions. The OS part is frozen on the main browsers, as above.
  • As a cache key. Varying a cached response on User-Agent creates one copy per distinct string, and there are very many distinct strings.

It is a good fit for reading logs, triaging a bug report ("it only breaks on this browser"), and counting browser families. RFC 9110 lists those uses: working out the scope of interoperability problems, and analytics on browser use.

Sources

Checked on 24 October 2026:

Frequently asked questions

It is a compatibility claim, not a rendering fact. KHTML-based browsers added it so servers that sent good pages to Gecko would send them to KHTML too, and WebKit and Chromium browsers kept it. Today it is on every Chrome and Safari string and tells you nothing.
No. MDN says almost every browser still sends Mozilla/5.0 for historical reasons. It has not changed in years and says nothing about the browser you are looking at.
Microsoft Edge ships a list of per-site overrides in its Stable and Beta channels. They replace the default string for specific sites that mishandle it, and you can see the list at edge://compat/useragent. Edge Canary and Dev do not apply the list.
Microsoft's own examples show an entry with that name next to Chromium and Microsoft Edge, and Microsoft warns that the order of brands can change. Look a brand up by name in the list. Never read a fixed position.
Yes, if the hint changes the response. MDN advises adding the hint to Vary so a cache keeps one copy per value. For hints with many values the cache entries multiply, so decide whether the page really needs to vary.
It stops honest crawlers and nothing else. Any client can send any string, so a rule on the User-Agent alone is a request, not a lock. Google documents a DNS check to confirm that a visitor claiming to be Googlebot is one.

Related