Moving away from raw user-agent strings
The user-agent string has always been an awkward part of the web platform: a passive fingerprinting surface that is notoriously noisy to parse. User-Agent Client Hints (UA-CH) offer a way to request and receive only the specific device and browser data your code actually needs. This guide walks through auditing current usage and migrating to the new API and header-based delivery.
Auditing where and why you collect user-agent data
Before changing anything, know exactly where user-agent data enters your stack and what decisions depend on it. Start by searching frontend code for navigator.userAgent, plus deprecated reads like navigator.platform and navigator.appVersion. On the backend, look for uses of the User-Agent HTTP header. Consider any logic that branches on browser name or version, operating system, device make or model, or CPU architecture/bitness.
If a third-party library or service parses user-agent data for you, verify that it has been updated to support Client Hints before you invest in your own changes.
Do you only need basic values?
Default Client Hints deliver browser name and major version (Sec-CH-UA), a mobile indicator (Sec-CH-UA-Mobile), and platform name (Sec-CH-UA-Platform). The reduced user-agent string proposal preserves exactly this basic data for backward compatibility. Code that checks terms like Chrome/90.0.4430.85 would instead see Chrome/90.0.0.0.
If you only parse browser, major version, or operating system name, existing code will keep working, though deprecation warnings will appear. Migration to UA-CH is still recommended, but this compatibility path means a gradual transition rather than a hard cutover.
Migration strategies
Choose the approach that fits where you process user-agent data today, or where you would prefer to process it going forward.
On-demand client-side JavaScript
For code currently calling navigator.userAgent, prefer navigator.userAgentData when available and fall back to parsing the string only when it isn't.
if (navigator.userAgentData) {
// new path using userAgentData
} else {
// legacy parse of navigator.userAgent
}
For a simple mobile versus desktop check, use the boolean mobile value:
userAgentData.brands returns an array of objects with brand and version fields. You can scan it with some() to locate a specific browser entry.
Information beyond the default set is considered high-entropy and requires a request. Values like full version, platform version, architecture, and device model are delivered asynchronously through a Promise:
This strategy is also the way to move processing from your server to the client, because the JavaScript API works without any HTTP request header access.
Static server-side header
If backend code reads the User-Agent header and your needs are uniform across the site, configure a static set of requested hints in a single place—your web server config, hosting settings, or the top-level config of your framework. That one-time setup is then applied to every response.
Use this strategy when responses themselves are transformed based on user-agent data. Clients may supply hints differently by default, so always declare the complete set you need. For Chrome today, declaring the defaults and an additional device-model request would look like:
On the server, first check whether the desired Sec-CH-UA header arrived. If not, fall back to legacy User-Agent parsing.
Delegating hints cross-origin
Requests to cross-origin subresources do not inherit the site's Accept-CH header. If https://blog.site uses resources on https://cdn.site that need hints such as device model, delegation happens through a Permissions-Policy header. The full list of policy-controlled hints is defined in the Client Hints Infrastructure draft spec.
For example, blog.site can ask for the Sec-CH-UA-Model hint and delegate that permission:
Multiple hints for multiple origins can appear in one header, including non-UA hints like Sec-CH-UA-Platform-Version.
Delegating hints into iframes
Cross-origin iframe delegation uses the allow attribute on the iframe element instead of a response header. Important: the allow attribute overrides whatever Accept-CH the embedded site might send. You must declare everything the iframed content will depend on.
Dynamic server-side hints
When specific routes need more granular data than the rest of the site, requesting hints only on those routes avoids a site-wide footprint. The controlling rule is that each Accept-CH response header entirely replaces the prior set. Every page served in this mode must therefore repeat the full hint list it depends on, never assuming hints were set earlier in the session.
Hints on the very first request
Default hints are available immediately without any declaration. A first request situation arises only when higher-entropy data is also required from the first page load. The Critical-CH header handles this by triggering an automatic browser retry with the additional header included. That first-request retry costs a round-trip, but the implementation overhead is minimal.
Where even that is insufficient, watch the Client Hints Reliability proposal. It uses the TLS 1.3 Application-Layer Protocol Settings (ALPS) extension to float hints ahead on new HTTP/2 and HTTP/3 connections. Still in an early stage, this is the area for teams that control their own TLS stacks.
Legacy code support
For code—particularly third-party snippets—that must keep reading navigator.userAgent beyond the reduced string's content, find interim relief in the UA-CH retrofill library. It rebuilds navigator.userAgent from whatever values your code requested through navigator.userAgentData. Request the model hint, for example, and the cooked string will reflect that value while preserving the reduced format for fields nobody explicitly asked for.



