X Xerobit

User-Agent Client Hints — Modern Browser Detection Without UA Sniffing

User-Agent Client Hints replace UA string parsing with structured HTTP headers. Learn how Sec-CH-UA headers work, how to request high-entropy hints, and how to use the...

Mian Ali Khalid · · 4 min read
Use the tool
User Agent Parser
Parse any User-Agent string into browser, OS, device, and engine. Or detect your own. Built on the maintained ua-parser-js dataset.
Open User Agent Parser →

User-Agent Client Hints (UA-CH) are structured HTTP headers that replace the opaque User-Agent string. Browsers send basic hints by default; servers must request additional detail.

Parse traditional user agent strings with the User-Agent Parser.

Default Client Hints (sent automatically)

Chromium browsers send these on every HTTPS request:

# Browser brand and version (low-entropy):
Sec-CH-UA: "Google Chrome";v="120", "Chromium";v="120", "Not-A.Brand";v="99"
Sec-CH-UA-Mobile: ?0     # 0 = desktop, 1 = mobile (boolean)
Sec-CH-UA-Platform: "Windows"  # OS name only

These three headers replace most UA-based detection for:

  • “Is this Chrome/Edge/Firefox?” → Sec-CH-UA
  • “Is this a mobile device?” → Sec-CH-UA-Mobile
  • “What OS?” → Sec-CH-UA-Platform

Request high-entropy hints via Accept-CH

For more detail (OS version, architecture, full version), respond with Accept-CH:

# Server response: tell browser to send additional hints on next request
HTTP/1.1 200 OK
Accept-CH: Sec-CH-UA-Full-Version-List, Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch

# On subsequent requests, browser sends:
Sec-CH-UA-Full-Version-List: "Google Chrome";v="120.0.6099.109", "Chromium";v="120.0.6099.109"
Sec-CH-UA-Platform-Version: "10.0.0"    # Windows 10
Sec-CH-UA-Arch: "x86"

Express middleware to set Accept-CH:

app.use((req, res, next) => {
  res.set('Accept-CH', [
    'Sec-CH-UA-Full-Version-List',
    'Sec-CH-UA-Platform-Version',
    'Sec-CH-UA-Arch',
    'Sec-CH-UA-Model',       // Device model (mobile only)
    'Sec-CH-UA-Bitness',     // "64" or "32"
    'Device-Memory',          // RAM in GB (rounded)
  ].join(', '));
  next();
});

All high-entropy Client Hint headers

Low-entropy (sent by default):
  Sec-CH-UA                    Brand list (e.g., Chrome, Chromium)
  Sec-CH-UA-Mobile             ?0 or ?1
  Sec-CH-UA-Platform           OS name ("Windows", "macOS", "Android")

High-entropy (require Accept-CH):
  Sec-CH-UA-Full-Version-List  Full browser versions with minor/patch
  Sec-CH-UA-Platform-Version   OS version ("10.0.0" for Win10)
  Sec-CH-UA-Arch               CPU architecture ("x86", "arm")
  Sec-CH-UA-Bitness            "64" or "32"
  Sec-CH-UA-Model              Device model (mobile: "Pixel 7")
  Sec-CH-UA-WoW64              ?1 if 32-bit browser on 64-bit OS
  Device-Memory                RAM in GB: 0.25, 0.5, 1, 2, 4, 8
  Downlink                     Network speed in Mbps (estimate)
  ECT                          Connection type: "4g", "3g", "2g", "slow-2g"
  RTT                          Round-trip time in ms (rounded)
  Save-Data                    "on" if user enabled data saver

JavaScript: navigator.userAgentData

In the browser, access client hints without parsing strings:

// Low-entropy data (synchronous):
const uaData = navigator.userAgentData;

uaData.brands;
// [{ brand: 'Google Chrome', version: '120' }, { brand: 'Chromium', version: '120' }, ...]

uaData.mobile;    // false
uaData.platform;  // "Windows"

// High-entropy data (asynchronous, requires user permission in some browsers):
const highEntropy = await navigator.userAgentData.getHighEntropyValues([
  'architecture',
  'model',
  'platform',
  'platformVersion',
  'fullVersionList',
  'bitness',
  'uaFullVersion',
]);

// {
//   architecture: "x86",
//   bitness: "64",
//   brands: [...],
//   fullVersionList: [{ brand: 'Google Chrome', version: '120.0.6099.109' }],
//   mobile: false,
//   model: "",
//   platform: "Windows",
//   platformVersion: "10.0.0",
//   uaFullVersion: "120.0.6099.109"
// }

Browser support and fallback

async function getBrowserInfo() {
  // Use Client Hints if available:
  if (navigator.userAgentData) {
    const data = await navigator.userAgentData.getHighEntropyValues(['platform', 'platformVersion']);
    return {
      browser: navigator.userAgentData.brands[0]?.brand,
      mobile: navigator.userAgentData.mobile,
      os: data.platform,
    };
  }
  
  // Fallback: parse User-Agent string
  const ua = navigator.userAgent;
  return {
    browser: /Chrome\//.test(ua) ? 'Chrome' : /Firefox\//.test(ua) ? 'Firefox' : 'Other',
    mobile: /Mobi|Android/i.test(ua),
    os: /Windows/.test(ua) ? 'Windows' : /Mac/.test(ua) ? 'macOS' : 'Other',
  };
}

Critical: Client Hints only work on HTTPS

HTTPS required for:
- Server-side Accept-CH to be honored
- navigator.userAgentData to work
- High-entropy hints to be sent

HTTP: browsers send only minimal/no client hints
localhost: treated as secure origin (works in development)

Privacy implications

Client hints are designed to reduce fingerprinting compared to the full UA string. High-entropy hints require explicit server opt-in per page/origin, and browsers may prompt users in future versions. The information is available in structured form rather than one long opaque string, making over-collection more visible.


Related posts

Related tool

User Agent Parser

Parse any User-Agent string into browser, OS, device, and engine. Or detect your own. Built on the maintained ua-parser-js dataset.

Written by Mian Ali Khalid. Part of the Dev Productivity pillar.