User-Agent Client Hints and navigator.userAgentData
The User-Agent Client Hints (UA-CH) API is a modern web standard
designed to replace the legacy, monolithic User-Agent string with a
structured, privacy-preserving mechanism for accessing browser and
device information. This article explains the fundamentals of User-Agent
Client Hints, explores how the JavaScript interface
navigator.userAgentData operates, and provides practical
code examples demonstrating how to access both basic and high-entropy
client data.
What is the User-Agent Client Hints API?
Historically, browsers transmitted a single User-Agent
(UA) string via HTTP request headers and exposed it in JavaScript
through navigator.userAgent. Over time, this string became
bloated, difficult to parse with regular expressions, and a significant
vector for passive user fingerprinting and tracking.
The User-Agent Client Hints API solves this by dividing client information into two tiers:
- Low-Entropy Hints: Basic information that does not easily identify an individual user. This data is available by default with zero performance or privacy overhead.
- High-Entropy Hints: Detailed device and system information (such as exact OS versions, device models, or full browser build numbers) that could aid in fingerprinting. This data is only accessible asynchronously when explicitly requested by the site and permitted by user preferences.
How
navigator.userAgentData Works
In supported browsers, the navigator.userAgentData
object provides direct access to client hint information in JavaScript.
It exposes immediate properties for low-entropy hints and a
promise-based method to query high-entropy hints.
1. Reading Low-Entropy Data
Low-entropy data is synchronously available on the
navigator.userAgentData object without requiring any
network permissions or asynchronous calls.
The core properties include: * brands: An array of
objects containing browser brand names and their major version numbers.
Browsers often include a “grease” brand to prevent servers from
hardcoding strict browser checks. * mobile: A boolean
indicating whether the user agent is running on a mobile device. *
platform: A string identifying the operating system
platform (e.g., "Windows", "macOS",
"Android", "Linux").
if ('userAgentData' in navigator) {
const { brands, mobile, platform } = navigator.userAgentData;
console.log('Brands:', brands);
// Example output: [{brand: "Chromium", version: "120"}, {brand: "Google Chrome", version: "120"}]
console.log('Is Mobile:', mobile);
// Example output: false
console.log('Platform:', platform);
// Example output: "macOS"
}2. Requesting High-Entropy Data
To retrieve specific device details, you must call the
getHighEntropyValues() method. This method accepts an array
of hint names and returns a Promise that resolves with an object
containing the requested values.
Supported high-entropy hints include: * architecture:
The CPU architecture (e.g., "x86", "arm"). *
bitness: The CPU bitness (e.g., "64",
"32"). * formFactor: The device type (e.g.,
"Desktop", "Phone", "Tablet"). *
model: The specific device model (primarily on mobile
devices). * platformVersion: The detailed version of the
operating system. * fullVersionList: The full version
numbers for all identified browser brands. * wow64: A
boolean indicating if a 32-bit application is running on 64-bit
Windows.
if ('userAgentData' in navigator) {
navigator.userAgentData.getHighEntropyValues([
'architecture',
'model',
'platformVersion',
'fullVersionList'
])
.then(hints => {
console.log('High-Entropy Hints:', hints);
/*
Example output:
{
architecture: "arm",
brands: [...],
fullVersionList: [{brand: "Google Chrome", version: "120.0.6099.109"}, ...],
mobile: false,
model: "",
platform: "macOS",
platformVersion: "14.2.1"
}
*/
})
.catch(error => {
console.error('Error fetching client hints:', error);
});
}Implementing Fallbacks for Unsupported Browsers
While modern Chromium-based browsers (Chrome, Edge, Opera, Samsung
Internet) support the User-Agent Client Hints API, other browsers like
Firefox and Safari have chosen alternative approaches to mitigate
fingerprinting (such as freezing the traditional User-Agent
string) and do not implement navigator.userAgentData.
To maintain compatibility across all environments, use feature
detection and fall back to navigator.userAgent:
function getClientPlatform() {
if ('userAgentData' in navigator && navigator.userAgentData.platform) {
return navigator.userAgentData.platform;
}
// Fallback for Safari, Firefox, and legacy browsers
const ua = navigator.userAgent;
if (/android/i.test(ua)) return 'Android';
if (/iPad|iPhone|iPod/.test(ua)) return 'iOS';
if (/Windows/i.test(ua)) return 'Windows';
if (/Mac/i.test(ua)) return 'macOS';
if (/Linux/i.test(ua)) return 'Linux';
return 'Unknown';
}Summary of Key Differences
| Feature | Legacy
navigator.userAgent |
Modern
navigator.userAgentData |
|---|---|---|
| Data Format | Unstructured plain text string | Structured JavaScript objects and arrays |
| Access Model | All data exposed immediately | Tiered (low-entropy sync, high-entropy async) |
| Privacy Impact | High passive fingerprinting risk | Controlled exposure via explicit requests |
| Parsing | Requires brittle regex patterns | Native property access
(.brands, .platform) |