User-Agent Client Hints

The replacement for the user agent string: less broadcast by default, more detail on request. Here is how it works and what you need to change.

The core idea

The user agent broadcasts everything to everyone, whether or not the site needs it. Client Hints invert that: browsers send a small, low-entropy set by default, and reveal anything more only when a site explicitly asks.

Chromium browsers send these on every request without being asked:

Sec-CH-UA: "Chromium";v="131", "Not_A Brand";v="24", "Google Chrome";v="131"
Sec-CH-UA-Mobile: ?0
Sec-CH-UA-Platform: "Windows"

That is deliberately close to useless for fingerprinting — browser family, a mobile boolean, and an OS name. Everything else must be requested.

High-entropy hints

These are the ones carrying real detail, and they are sent only on request:

HeaderExampleReplaces
Sec-CH-UA-Platform-Version"15.0.0"The OS version, now frozen in the UA
Sec-CH-UA-Model"Pixel 9 Pro"The Android model, now reported as K
Sec-CH-UA-Arch"arm"CPU architecture; the only way to spot Apple Silicon
Sec-CH-UA-Bitness"64"32- or 64-bit
Sec-CH-UA-Full-Version-List"Google Chrome";v="131.0.6778.86"The full version, now 0.0.0 in the UA
Sec-CH-UA-WoW64?032-bit process on 64-bit Windows
Sec-CH-UA-Form-Factors"Desktop"Form factor, explicitly rather than by inference

Requesting them

Send an Accept-CH response header naming what you need. The browser includes those headers on subsequent requests to your origin.

Accept-CH: Sec-CH-UA-Platform-Version, Sec-CH-UA-Model, Sec-CH-UA-Arch

In Apache, that is one line in .htaccess:

Header set Accept-CH "Sec-CH-UA-Platform-Version, Sec-CH-UA-Model, Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version-List"
The first request never has them. Accept-CH only takes effect from the second request onward, because the browser has to see the header before it knows to send anything. If you need the detail on the very first hit, read it in JavaScript instead.

Reading them in JavaScript

Low-entropy values are available synchronously; high-entropy ones require an async call, which is also what gates them behind permission policy.

// Low entropy — available immediately
navigator.userAgentData.platform;   // "Windows"
navigator.userAgentData.mobile;     // false
navigator.userAgentData.brands;     // [{brand:"Google Chrome", version:"151"}, ...]

// High entropy — returns a Promise
const hints = await navigator.userAgentData.getHighEntropyValues([
  'architecture', 'bitness', 'model',
  'platformVersion', 'fullVersionList'
]);
console.log(hints.platformVersion);  // "16.0.0"  -> Windows 11
console.log(hints.fullVersionList);  // [{brand:"Google Chrome", version:"151.0.7922.109"}]

Our detector makes exactly this call and merges the result into the parse, which is how it can resolve Windows 11 and your real Android model despite the frozen string.

Detecting Windows 11

The single most-asked question about Client Hints. The user agent reports Windows NT 10.0 for both Windows 10 and 11, so the platform version is the only signal:

const { platformVersion } = await navigator.userAgentData
  .getHighEntropyValues(['platformVersion']);

const major = parseInt(platformVersion.split('.')[0], 10);
const version = major >= 13 ? 'Windows 11' : 'Windows 10';

Browser support

Client Hints are Chromium-only: Chrome, Edge, Opera, Samsung Internet, Brave and Vivaldi. Firefox and Safari have not implemented them and have both signalled they do not intend to, citing the same fingerprinting concerns the feature was meant to address — the request-based model still lets a determined site collect everything.

The practical consequence is that you need both paths indefinitely: Client Hints where available, user agent parsing everywhere else. Which is precisely what our parser does — it parses the string, then overlays hints when the browser provides them.

Frequently asked questions

Will Client Hints fully replace the user agent?

Unlikely, at least for a long time. Firefox and Safari have declined to implement them, and non-browser clients — cURL, crawlers, SDKs — send only a user agent. The realistic end state is a reduced user agent that everyone sends, with Client Hints as an optional enrichment on Chromium.

Do Client Hints work cross-origin?

Not by default. Hints are scoped to the origin that requested them. To share them with a third-party origin such as an analytics endpoint, you must delegate explicitly using a Permissions-Policy header naming that origin.

Why does Sec-CH-UA include "Not_A Brand"?

It is deliberate cache-busting for parsers, known as GREASE. Chrome injects a randomised fake brand so that code parsing the list is forced to handle unknown entries gracefully, rather than hard-coding an expected set that breaks the moment a new browser appears. Ignore any brand matching the not-a-brand pattern.

Are Client Hints better for privacy?

Somewhat, but they are not a privacy feature in the strong sense. The default set is genuinely smaller than the old user agent. A site that wants the full picture can still request everything, and most large sites do — the gain is that sites which do not need the detail no longer receive it passively.