# ua_client_hints

> Chromium's User-Agent Client Hints, without version numbers: brands (GREASE removed), mobile, platform, and the stable high-entropy hints.

Source: https://whiskerjs.com/signals/ua-client-hints/ (last updated 2026-10-26)

**ua_client_hints at a glance (from the library code, signalsVersion 9)**

| Field | Value |
| --- | --- |
| Component name | `ua_client_hints` |
| In the visitorId | yes |
| structuredId group | [browser (engine and build)](https://whiskerjs.com/docs/identifiers/#structuredid) |
| Hashing | plain value; a digest only when longer than 128 characters or not printable ASCII |
| Excluded under | no browser class |
| Inner timeout | 1000 ms |
| Added in | [signalsVersion 2](https://whiskerjs.com/whats-new/) |

> **In plain words:**
>
> Chromium-based browsers (Chrome, Edge, Brave, Samsung Internet and others) describe themselves in a structured way called User-Agent Client Hints: the browser brands, whether it is a phone, the operating system, and on request a few more details such as the CPU architecture. WhiskerJS keeps the parts that rarely change and leaves out version numbers. Other browsers do not offer this, and the component then says so.

## What it reads

From `navigator.userAgentData`:

- `brands`: the brand names, without their versions;
- `mobile` and `platform`;
- from one `getHighEntropyValues()` call: `architecture`, `bitness`, `wow64`, `model` and `formFactors`.

The same call also asks for `fullVersionList` and `platformVersion`, which go to the separate [`ua_versions`](https://whiskerjs.com/signals/ua-versions/) component. One request per collection is shared by both.

## How the technique works

1. If `navigator.userAgentData` is not an object, the component is `unavailable` with `no_api`.
2. Brands are filtered: any brand that reads `notabrand` once reduced to lower-case letters is a [GREASE](https://whiskerjs.com/glossary/#grease) brand (for example `Not_A Brand` or `Not)A;Brand`) and is dropped. The rest are sorted by name.
3. `getHighEntropyValues([...])` is called once. Booleans become `1` or `0`; form factors are sorted and joined with `+`.
4. The value is `brands=...;mobile=...;platform=...;arch=...;bitness=...;wow64=...;model=...;form=...`.
5. If the detailed call is refused (`NotAllowedError` or `SecurityError`, as when a [Permissions Policy](https://whiskerjs.com/glossary/#permissions-policy) blocks it), the end of the value is the stable marker `he=blocked`. Another error gives `he=failed`; a browser without `getHighEntropyValues` gives `he=none`.

## Why it helps

It states the device class and architecture more reliably than the user agent string, which Chromium has reduced: `mobile`, the platform, `arm` or `x86`, 32 or 64 bit, the Windows-on-Windows flag, the Android model name and the form factor. Brand names separate Chrome, Edge, Brave and other Chromium builds. Without versions the value stays put across browser updates.

## Stability

- Brands without versions, platform and architecture rarely change.
- A new GREASE brand in a browser update does not change the value, because GREASE brands are filtered.
- The value changes if the browser renames a brand, if the page context changes from allowed to refused for the detailed hints (the `he=blocked` marker replaces the details), or when the device or form factor changes.

## Browser support and what blocks it

| Browser | What you get |
| --- | --- |
| [Chrome desktop](https://whiskerjs.com/browsers/chrome-desktop/), [Chrome Android](https://whiskerjs.com/browsers/chrome-android/), [Edge](https://whiskerjs.com/browsers/edge/), [Brave](https://whiskerjs.com/browsers/brave/), [Samsung Internet](https://whiskerjs.com/browsers/samsung-internet/) | `ok` (Chromium-based) |
| [Safari](https://whiskerjs.com/browsers/safari-macos/), [every iOS browser](https://whiskerjs.com/browsers/safari-ios/), [Firefox](https://whiskerjs.com/browsers/firefox/) | `unavailable` / `no_api` by design |
| Chromium in a frame or page whose Permissions Policy refuses the detailed hints | `ok` ending in `he=blocked` |

## Privacy

The low-entropy part (brands, mobile, platform) is what Chromium already sends to servers by default. The detailed hints add the architecture, bitness and, on Android, the model name. WhiskerJS requests no version numbers for this component, makes no request and shows no prompt. Values over 128 characters or with non-ASCII characters (for example an unusual model name) are hashed.

## How WhiskerJS handles noise and failures

| Status | ruleId | When |
| --- | --- | --- |
| `ok` |   | the value, with details or a `he=blocked`, `he=failed` or `he=none` marker |
| `unavailable` | `no_api` | no `navigator.userAgentData` |
| `unavailable` | `no_digest` | the value needs hashing and there is no `crypto.subtle` |
| `excluded` | `caller_excluded` | you excluded it |
| `timed_out` | `timeout`, `aborted` | the detailed hints did not answer within 1000 ms, or budget or abort |

Errors of the detailed call never fail the component; they become a marker. Your server can compare the `platform` here with the user agent header it receives; see [Recommended server checks](https://whiskerjs.com/docs/integration/#recommended-server-checks).

## Example value

From headless Chromium in a Linux container: `brands=Chromium,HeadlessChrome;mobile=0;platform=Linux;arch=arm;bitness=64;wow64=0;model=;form=Desktop`. Firefox and WebKit in the same run: `unavailable` / `no_api`. A desktop Chrome on a Mac looks like `brands=Chromium,Google Chrome;mobile=0;platform=macOS;arch=arm;bitness=64;wow64=0;model=;form=Desktop`.

## Questions and answers

### What is the GREASE brand and why is it removed?

Chromium adds a deliberately odd brand such as Not_A Brand that changes between versions, so that sites do not depend on an exact brand list. WhiskerJS drops any brand that reads notabrand once reduced to letters, so the id does not change with it.

### Does getHighEntropyValues prompt the user?

No. It is a promise-based read with no prompt and no network request. A page or frame can be refused by a Permissions Policy, which WhiskerJS reports with the stable marker he=blocked.

### Why are version numbers not in this component?

They change with every browser and operating system update. They are reported separately in ua_versions, which is outside both ids.

### Why is it unavailable in Safari and Firefox?

Only Chromium-based browsers implement navigator.userAgentData. Elsewhere the component is unavailable with ruleId no_api, by design.

## Related pages

- [ua_versions](https://whiskerjs.com/signals/ua-versions/): The full version numbers, kept out of the ids.
- [Client Hints](https://whiskerjs.com/glossary/#client-hints): What User-Agent Client Hints are.
- [GREASE](https://whiskerjs.com/glossary/#grease): Why brand lists contain a fake brand.
- [Permissions Policy](https://whiskerjs.com/glossary/#permissions-policy): How a page can refuse the detailed hints.
- [platform](https://whiskerjs.com/signals/platform/): navigator.platform, available in every browser.
