Sec-CH-UA and User-Agent Client Hints explained (tested in Chromium)
User-Agent Client Hints are the Sec-CH-UA* request headers Chromium-based browsers send alongside (and eventually instead of) the User-Agent string. Three are sent by default; the rest only after the server asks with Accept-CH. We logged every one on a test server to see exactly when each appears.
1. What Chromium sends by default
First visit to http://localhost:8965/ in a fresh profile, with no Accept-CH anywhere. These three low-entropy hints arrived on the navigation, on fetch(), on an <img> and on the favicon request:
sec-ch-ua: "Chromium";v="153", "Not_A Brand";v="8"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Linux"
user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/153.0.0.0 Safari/537.36
"Not_A Brand";v="8"is GREASE: a deliberately fake brand that tells servers to parse the list properly instead of matching the whole string. Its spelling and version are not fixed, so never compare against it; look for the brand you need ("Chromium","Google Chrome","Microsoft Edge") in the list.- Only the major version is in
sec-ch-ua. The full version (153.0.8010.12) needs a high-entropy hint. ?0/?1are structured-header booleans:?0means not mobile.- The headless build put
HeadlessChromein the User-Agent string but still sent"Chromium"insec-ch-ua, so the two headers can disagree.
2. Asking for more with Accept-CH
The server answered a page with:
Accept-CH: Sec-CH-UA-Platform-Version, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Arch, Sec-CH-UA-Model
| Request | High-entropy hints received? |
|---|---|
| The navigation whose response carried Accept-CH | No, only the default three |
fetch(), <img>, favicon from that page | Yes, all four |
| Next navigation on the same origin | Yes |
| A later navigation after a page without Accept-CH | Yes (the preference was remembered) |
http://127.0.0.1:8965 (different origin) | No, only the default three |
Cross-origin fetch() to 127.0.0.1 from that page | No, only the default three |
Values received on Linux x86_64:
sec-ch-ua-full-version-list: "Chromium";v="153.0.8010.12", "Not_A Brand";v="8.0.0.0"
sec-ch-ua-arch: "x86"
sec-ch-ua-platform-version: ""
sec-ch-ua-model: ""
Note the empty strings: platform version was blank on Linux and model is blank on desktop. Code that reads these must handle "". A <meta http-equiv="Accept-CH" content="Sec-CH-UA-Arch"> tag behaved differently from the header: the page's own fetch() got sec-ch-ua-arch, but the next navigation did not, so the meta tag is not remembered.
3. Critical-CH: getting hints on the very first request
Adding Critical-CH: Sec-CH-UA-Platform-Version next to Accept-CH made Chromium retry the same navigation: our server logged 2 hits for one page.goto(), the first with the default three hints, the second with sec-ch-ua-platform-version and sec-ch-ua-full-version-list. The next navigation was a single hit carrying both hints. Use it only when the first response truly depends on the hint; it costs a full extra round trip on first visit.
4. Third-party requests need Permissions-Policy
With only Accept-CH, the cross-origin request to 127.0.0.1 got the default three hints. Adding
Permissions-Policy: ch-ua-arch=(self "http://127.0.0.1:8966"), ch-ua-full-version-list=(self "http://127.0.0.1:8966")
to the page response made the same cross-origin fetch() carry sec-ch-ua-arch and sec-ch-ua-full-version-list.
5. Secure contexts only
| Origin | isSecureContext | Sec-CH-UA headers | navigator.userAgentData |
|---|---|---|---|
http://localhost:8965 | true | Yes, Accept-CH honoured | present |
http://127.0.0.1:8965 | true | Yes, Accept-CH honoured | present |
http://plain.test:8965 (mapped to 127.0.0.1) | false | None, even after Accept-CH | undefined |
So hints work on local dev but disappear on a plain-http staging host. Serve over HTTPS if your code depends on them. curl 8.14.1 sent only host, user-agent: curl/8.14.1 and accept: */*, and Node.js 24 fetch() sent no sec-ch-* headers either: non-browser clients send none.
6. The JavaScript side
navigator.userAgentData.brands
// [{brand:"Chromium",version:"153"},{brand:"Not_A Brand",version:"8"}]
await navigator.userAgentData.getHighEntropyValues(
["architecture","bitness","model","platformVersion","fullVersionList","wow64","formFactors"])
// architecture:"x86", bitness:"64", model:"", platformVersion:"",
// fullVersionList:[{brand:"Chromium",version:"153.0.8010.12"},{brand:"Not_A Brand",version:"8.0.0.0"}],
// wow64:false, formFactors:["Desktop"], mobile:false, platform:"Linux", uaFullVersion:"153.0.8010.12"
No Accept-CH was needed for the JavaScript API. If your response changes based on any hint, add it to Vary (for example Vary: Sec-CH-UA-Mobile) so caches keep the variants apart; see Vary and caching. Related request headers: Sec-Fetch-Site, Mode and Dest and Accept-Language.
Frequently asked questions
What is the sec-ch-ua header?
A User-Agent Client Hint listing the browser's brands with major versions. Chromium 153 sent "Chromium";v="153", "Not_A Brand";v="8" by default on every request to a secure origin.
What does Not_A Brand mean in sec-ch-ua?
It is GREASE: an intentionally fake brand entry so servers parse the list instead of matching an exact string. Its name and version can change, so ignore it and look for the brand you care about.
Why is Sec-CH-UA-Platform-Version not sent?
It is a high-entropy hint: the server must request it with Accept-CH first. In Chromium 153 the response carrying Accept-CH did not get it, the following requests did. Add Critical-CH to make the browser retry the first navigation with it. On Linux its value was an empty string.
Why are client hints missing on my site?
They are only sent to secure contexts. localhost and 127.0.0.1 over http got them, but a plain-http host name mapped to 127.0.0.1 got no Sec-CH-UA headers at all, even after Accept-CH, and navigator.userAgentData was undefined.
Are client hints sent to third-party requests?
Only the default three. A cross-origin fetch got sec-ch-ua-arch and sec-ch-ua-full-version-list only after the page delegated them with Permissions-Policy: ch-ua-arch=(self "origin").
Does curl send sec-ch-ua?
No. curl 8.14.1 sent only Host, User-Agent and Accept, and Node.js 24 fetch sent no sec-ch-* headers. Client hints come from browsers.
Written with AI assistance. Measured on 2026-10-01 in headless Chromium 153.0.8010.12 on Linux x86_64 (Playwright, channel 'chromium', fresh context per test) against a Node.js 24.21.0 http server on localhost:8965-8966 that logged every Sec-CH-UA* header and counted hits. 127.0.0.1 and a host name mapped to 127.0.0.1 with --host-resolver-rules (plain http, not localhost) were used to test secure-context rules. curl 8.14.1 and Node.js fetch were logged by the same server. Other browsers were not tested. PlainHeaders is our extension; DevTools → Network → Headers shows the same request headers, free.