# ua-client-hints: replacing User-Agent sniffing with a set of headers

> User Agent Client Hints is a specification rather than a library, hosted by the WICG, that adds a family of Sec-CH-UA HTTP headers and a matching JavaScript API so that a server can adapt a response without parsing the User-Agent string. The specification also carries an unusually long explainer on why the User-Agent string is a problem, and what the objections to it are.

**WICG/ua-client-hints** — Wouldn't it be nice if `User-Agent` was a (set of) client hints?

- Repository: https://github.com/WICG/ua-client-hints
- Website: https://wicg.github.io/ua-client-hints/
- Stars: 623 · Forks: 91
- Language: Bikeshed
- License: NOASSERTION
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/wicg-ua-client-hints

## A specification repository, not a library

What lives in this repository is the specification text. The source is a Bikeshed file, index.bs, which is the markup used to write web standards, and the repository also carries a preview configuration for rendering pull requests, a JSON file describing the group's metadata, a contributing guide, a code of conduct and a licence file. There is no package to install and no code to call.

The project page is hosted under the WICG, the web incubator community group, and the topic list on the repository mentions shipping in Chromium, which is the one implementation signal the repository itself carries. The specification is titled User Agent Client Hints, and its stated aim appears in the description as a question: would it not be nice if User-Agent was a set of client hints.

That framing is the whole proposal. Instead of one string that a server has to parse, understand and guess at, the client sends a set of discrete, named pieces of information, each with a defined meaning, and the server asks for the ones it cares about.

## The headers the specification introduces

The specification introduces a set of Client Hints HTTP headers, following the header convention defined in RFC 8942, and a corresponding JavaScript API. The headers are the client architecture, the bitness, whether the client is mobile, the device model, the platform, the platform version, the general UA hint, the form factors, the full version, the list form of the full version, and a 64 bit indicator.

One of them is already superseded. Sec-CH-UA-Full-Version is marked as deprecated in favour of Sec-CH-UA-Full-Version-List, which is the same idea expressed as a structured list rather than a single value, and it is the kind of detail that matters if you are writing a server that consumes these headers today.

The names use a Sec-CH- prefix, and the specification has a section explaining why, alongside entries on where the underlying Client Hints infrastructure is specified, and on how the mobile hint defines mobile at all. Those three questions are the ones a server implementer asks first, and having them answered inside the same document is the point of the FAQ.

## The problem: entropy shipped in every request

The problem section starts with something everyone knows and rarely puts in those terms. User agents identify themselves to servers as part of each HTTP request through the User-Agent header, and that header's value has grown in both length and complexity over the years. What follows is a description of a complicated dance, with server-side sniffing to provide the right experience for the right devices on one side, and client-side spoofing to bypass incorrect or inconvenient sniffing on the other.

The examples make the point concretely. Chrome on iOS identifies itself as an iPhone Safari, and Chrome on Android sends something closer to a Chrome on a Pixel:
```http
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/69.0.3497.105 Mobile/15E148 Safari/605.1
```
```http
User-Agent: Mozilla/5.0 (Linux; Android 9; Pixel 2 XL Build/PPP3.180510.008) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Mobile Safari/537.36
```
The argument that follows is about entropy. There is a lot of entropy wrapped up in the string that is sent to servers by default, for all first and third-party requests, which makes it an important part of fingerprinting schemes of all sorts, since a server can passively capture the information without the user's awareness or ability to prevent the collection. The document notes that Safari has taken some steps to reduce the entropy of its user agent string, initially locking it to a single value and then backing off a bit after developer feedback, and proposes a mechanism that might let user agents be a little more aggressive.

## Developers object, and the explainer takes them seriously

The specification does not pretend the change is free. From a developer's standpoint the detail in the UA string is valuable, and developers reasonably object to dropping it, and the feedback about Safari's freeze is described as being along those lines.

The first category of use is brand and version information, which lets a site work around known bugs in specific releases that are not otherwise detectable. The example given is Content Security Policy, whose implementations have varied widely between vendors, so it is difficult to know which policy to send in a response without knowing which browser is responsible for parsing and executing it. That is a concrete, hard case: a client hint that says which browser is asking does not tell you which bugs that browser has.

The second category is content negotiation by user agent and platform, with the observation that some application frameworks style an application differently on iOS than on Android to match each platform's aesthetic and design patterns. The document is honest that the categories continue beyond the two that fit in the visible excerpt, and the pattern is clear: each use case is something a developer does today by parsing a string, and each one needs a piece of information that the string happens to carry.

The specification's response to that is differential serving, which the use case list treats as the primary mechanism.

## The use cases the explainer enumerates

The table of contents of the explainer is effectively a catalogue of what the web currently does with the User-Agent string, and it is worth reading as a list of things you might have to migrate.

Differential serving appears twice, once based on browser features and once as a browser bug workaround, which is the first category above restated. Marketshare analytics is the second, and it is the obvious use of a platform or form factor hint. Content adaptation covers the rest, with a browser based adaptation entry, a mobile specific site, low-powered devices, OS specific styles, OS integration, and browser and OS specific experiments.

Then there are the less obvious ones. User login notification, so a server can tell a client to show that an account was used elsewhere. Download of appropriate binary executables, so a site serves the right build. Conversion modeling, which is analytics with a different name. Vulnerability filtering, so a server can refuse a request from a client with a known flaw. Logs and debugging. Fingerprinting, split into spam filtering and bot detection on one side and persistent user tracking on the other, which is the one place where the proposal is used to make tracking harder. And blocking known bots and crawlers.

The value of the list is that it doubles as a migration checklist for a server team, whether you are moving towards the hints or just trying to find out what your current sniffing is actually for.

## The alternatives that were considered and rejected

A specification that changes a request header for every site on the web should have looked at the alternatives, and this one does. The considered alternatives section lists two.

The first is freezing the User-Agent string and reducing its information density without providing an alternative mechanism. That is the Safari approach as it was first attempted, and the document describes the feedback that came back, which is that developers need the detail and there is nothing to replace it with. The second is restructuring the User-Agent string, keeping one header but changing its shape so that a server can read the parts it cares about instead of parsing a free-form string.

Both are close cousins of what is being proposed, and both are rejected for the same underlying reason: they change what the client sends without giving developers a better way to ask. The Client Hints approach keeps the low entropy string and adds a negotiation mechanism on top, so the client can send less by default and more when the server asks for it. The mention of Safari backing off after developer feedback is the empirical version of the same lesson.

## The open questions the FAQ keeps returning to

The FAQ in the explainer is unusually candid for a specification, and its questions are the ones a server team will be arguing about internally.

The first is whether the JavaScript interface has to be neutered as well. If the headers stop advertising the details but Navigator.userAgent still returns the full string, a page can read the same information anyway, and the entropy argument only half holds. The second is the compatibility hit from UA sniffing: if hints are not sent by every client, servers have to keep the old path working, and the document does not pretend otherwise. The third is whether the UA string should really be a set, which is the question the whole proposal answers, and it is asked because the answer has consequences for compatibility.

The rest are shorter and equally practical. Where the Client Hints infrastructure is specified, since the headers depend on it. What the Sec-CH- prefix means and why it is there. How the mobile hint defines mobile, which matters because mobile is a word that different vendors have defined differently. Whether the specification is duplicating information already in the User-Agent header, which it openly acknowledges while arguing the duplication is the point. Whether a lot of new headers will bloat requests, which is the cost of the negotiation model. And why a 64 bit hint exists at all, which is a question about the long tail of 32 bit clients that still need to be served.

The last push to the repository was on 2026-10-06, and the document is a living explainer rather than a frozen specification, so those answers can change.

## Conclusion

ua-client-hints suits the standards minded: engineers who want to understand why the web is moving away from User-Agent strings before their own stack has to deal with it, because the explainer covers both the problem and the counterarguments in a way few specifications do. It is not something you adopt, install or vendor, and it is the wrong starting point if you are trying to pick a UA parsing library today, since the document does not tell you how to serve clients that have not adopted the headers. Before you plan around it, read the FAQ entries on the compatibility hit from UA sniffing and on whether the JavaScript interface should be neutered too, because those two answers decide how much of your existing sniffing you can remove, and check the deprecation note on the full version header, since it has been superseded by the list variant. The repository is a Bikeshed source with a preview configuration, and the last push was on 2026-10-06.

## FAQ

### What is User Agent Client Hints?

It is a specification that introduces a family of Client Hints HTTP headers and a corresponding JavaScript API, so that a server can learn about the client from named values rather than by parsing the User-Agent string. The headers cover architecture, bitness, mobile, model, platform, platform version, form factors and the full version.

### Which header replaced Sec-CH-UA-Full-Version?

Sec-CH-UA-Full-Version is deprecated in favour of Sec-CH-UA-Full-Version-List, which expresses the same information as a structured list rather than a single value.

### Why does the specification want to reduce the User-Agent string?

Because a lot of entropy is wrapped up in the string that is sent by default for all first and third-party requests, which makes it an important part of fingerprinting, and a server can capture it passively without the user's awareness. The document notes that Safari took steps in the same direction by locking its string to a single value before backing off after developer feedback.

### Will Navigator.userAgent be removed as well?

That is an open question in the specification's own FAQ, which asks whether the JavaScript interface has to be neutered too. The related concern is the compatibility hit from UA sniffing, since clients that do not send the hints leave servers maintaining the old code path.

### What alternatives to client hints were considered?

Two are listed in the considered alternatives section: freezing the User-Agent string and reducing its information density without providing an alternative mechanism, and restructuring the User-Agent string. Both are rejected in favour of keeping a low entropy string and adding negotiation on top of it.

## Sources

- [Issues](https://github.com/WICG/ua-client-hints/issues)
- [Project website](https://wicg.github.io/ua-client-hints/)
- [README](https://github.com/WICG/ua-client-hints/blob/main/README.md)
- [WICG/ua-client-hints on GitHub](https://github.com/WICG/ua-client-hints)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/wicg-ua-client-hints
