User-Agent Client Hints (UA-CH) are opt-in HTTP request headers — all prefixed Sec-CH-UA — that replace the single, sprawling User-Agent string with structured fields a site receives only when it asks for them. By default a Chromium browser sends just three low-entropy hints (Sec-CH-UA, Sec-CH-UA-Mobile, Sec-CH-UA-Platform). Detailed values like the device model, full browser version, and CPU architecture are high-entropy and stay hidden until the server advertises interest with an Accept-CH response header. The design flips the old model: instead of every site passively receiving everything, sites must actively request the data they need, and that request is visible and auditable.
That is the summary an AI Overview will give you. Here is what it can't show you: the actual two-round-trip request flow, the exact split between which hints are free and which cost an opt-in, and the browser-support reality that quietly breaks any "just switch to Client Hints" plan. The diagrams, tables, and header samples below are the parts that matter when you are actually implementing detection.
The request flow: why UA-CH takes a round trip
The single most important thing to understand about UA-CH is that high-entropy hints are not on the first request. The browser has to be told the origin wants them, then it includes them on the next request. This is the mechanism the legacy User-Agent string never had, and it is why naive server code "sees nothing."
If a hint has to be present on the very first navigation — say you serve different assets based on platform version at the CDN edge — add a Critical-CH header alongside Accept-CH. The browser will retry the initial request with the critical hints attached rather than making you wait for a second navigation.
Low-entropy vs high-entropy: what you get for free
The whole privacy argument rests on this split. Low-entropy hints reveal little about an individual and ship by default. High-entropy hints narrow a user down and require the opt-in above.
| Header | Entropy tier | Sent by default? | Example value |
|---|---|---|---|
Sec-CH-UA | Low | Yes | "Chromium";v="126", "Not(A:Brand";v="24", "Google Chrome";v="126" |
Sec-CH-UA-Mobile | Low | Yes | ?0 |
Sec-CH-UA-Platform | Low | Yes | "Windows" |
Sec-CH-UA-Platform-Version | High | No (Accept-CH) | "15.0.0" |
Sec-CH-UA-Arch | High | No (Accept-CH) | "x86" |
Sec-CH-UA-Bitness | High | No (Accept-CH) | "64" |
Sec-CH-UA-Model | High | No (Accept-CH) | "Pixel 8" |
Sec-CH-UA-Full-Version-List | High | No (Accept-CH) | "Chromium";v="126.0.6478.126", ... |
| Which should I use? | — | — | Request only the high-entropy hints you actually branch on; keep the default three otherwise. |
The values are Structured Field values (RFC 9651, which supersedes RFC 8941): booleans render as ?0/?1, strings are double-quoted, and lists are comma-separated. Parse them with a Structured Fields library, not a regex — that is a core reason the format exists.
GREASE: the fake brand that keeps the format honest
Look again at that Sec-CH-UA example: "Not(A:Brand";v="24". That entry is not a real browser. It is GREASE — a deliberately fake brand with randomized punctuation and version that Chromium injects so that servers cannot hard-code an exact match against the brand list.
The GREASE brand's name, punctuation, and version rotate between Chrome builds. The lesson for anyone writing detection code: iterate the list looking for the brands you care about, and tolerate entries you have never seen. This is the same anti-ossification trick TLS uses.
Reading Client Hints in JavaScript
Server-side you read the headers. Client-side you read navigator.userAgentData. The low-entropy fields are synchronous; the high-entropy fields come back as a Promise so the browser can gate them behind permission policy.
if (navigator.userAgentData) {
// Low-entropy, available immediately
console.log(navigator.userAgentData.brands); // [{brand, version}, ...]
console.log(navigator.userAgentData.mobile); // boolean
console.log(navigator.userAgentData.platform); // "Windows"
// High-entropy, async and opt-in
const hints = await navigator.userAgentData.getHighEntropyValues([
"platformVersion", "model", "architecture", "fullVersionList",
]);
console.log(hints.platformVersion, hints.model);
} else {
// Firefox / Safari / non-secure context: fall back to navigator.userAgent
}
The else branch is not optional. navigator.userAgentData is undefined in Firefox and Safari, and also undefined over plain HTTP because Client Hints only operate in secure contexts.
The catch nobody puts in the summary: it's Chromium-only
The reason "just migrate to Client Hints" is bad advice is browser support. UA-CH ships in Chrome, Edge, Opera, and other Chromium browsers. Mozilla publicly assessed the full proposal as "harmful," and Apple's WebKit has not implemented it — so in Firefox and Safari there are no Sec-CH-UA headers and no navigator.userAgentData at all.
That means for the foreseeable future you maintain two paths: parse Client Hints where they exist, and fall back to the (now version-frozen, "reduced") User-Agent string everywhere else. Chrome's own UA-reduction effort froze the minor version and generalized the platform in the legacy string, so the fallback is coarser than it used to be — but it is still the only signal cross-browser. If you are untangling real-world traffic, our User-Agent Parser handles both the legacy string and the newer hint structure client-side, so nothing you paste leaves the browser.
Does UA-CH actually help privacy?
Partially, and it is worth being precise. The privacy win is that detailed, individuating data is no longer broadcast to every third party by default, and the Accept-CH opt-in is observable — you can audit which origins are asking for high-entropy hints. But a site determined to fingerprint can simply request every high-entropy hint and rebuild most of what the old string leaked. UA-CH shrinks the passive, default attack surface and makes data collection legible; it does not make browser fingerprinting go away. Treat it as a structural improvement, not a privacy guarantee.
Bottom line
UA-CH is a genuine improvement in how browser detection works: structured headers, an explicit opt-in for sensitive data, GREASE to keep the format evolvable, and a clean JS API. But it is Chromium-only, high-entropy hints cost a round trip, and it does not eliminate fingerprinting. Build detection that reads hints where available and falls back to the User-Agent string everywhere else — and request only the hints you actually branch on.