Back to Knowledge Hub
Platform

User-Agent Reduction and a Practical Client Hints Migration

A standards-based migration from brittle User-Agent parsing to User-Agent Client Hints, with privacy, compatibility, and BotBrowser boundaries.

BotBrowser Team

Documentation

Want the structured docs for Platform?

This article lives in the editorial library. For step-by-step setup, reference material, and ongoing updates, jump into the docs section.

User-Agent reduction is a compatibility change, not a signal to invent a new fingerprint. Sites should read the smallest public signal needed for a task, request additional hints only when justified, and keep a tested fallback. BotBrowser can compare these signals in an authorized controlled context, while the site and application remain responsible for the final decision.

Flow from a reduced User-Agent through Client Hints to a product decision, fallback, and privacy review

TL;DR

Chrome's User-Agent Reduction freezes or generalizes parts of the legacy User-Agent string. UA-CH moves selected details into structured request headers and navigator.userAgentData; high-entropy values are available only under the browser's permission and policy rules. Migrate by inventorying every parser, preferring feature detection, sending Accept-CH only for a declared need, and testing old and new paths together. BotBrowser can provide controlled, authorized contexts for this comparison, but it cannot guarantee a site's interpretation or a universal browser result.

Contents

What changed

The legacy string is exposed in the User-Agent request header and navigator.userAgent. Reduction deliberately removes or freezes detail so passive collection carries less identifying information. UA-CH provides structured low-entropy hints such as Sec-CH-UA, Sec-CH-UA-Mobile, and Sec-CH-UA-Platform, mirrored by navigator.userAgentData.brands, mobile, and platform.

Detailed values such as platformVersion, architecture, bitness, model, and fullVersionList are high entropy. A server can request selected values with Accept-CH; a page can ask for the corresponding JavaScript values with getHighEntropyValues(). Availability is contextual: secure transport, browser policy, permissions, and user settings can affect the result.

const lowEntropy = {
  brands: navigator.userAgentData?.brands ?? [],
  mobile: navigator.userAgentData?.mobile ?? null,
  platform: navigator.userAgentData?.platform ?? 'unknown',
};
const detailed = navigator.userAgentData
  ? await navigator.userAgentData.getHighEntropyValues(['platformVersion', 'architecture'])
  : null;

Do not treat a missing API as proof that a device is a particular model or that a request is automated. It is simply an unavailable signal.

A migration sequence

  1. Inventory dependencies. Find server parsers, analytics fields, CDN rules, framework checks, and tests that assume a full OS or minor browser version in User-Agent.
  2. Define the product decision. If the decision is capability-based, use feature detection or a standards API. If a server genuinely needs a platform family, use a low-entropy hint and document the purpose.
  3. Add a bounded fallback. Older browsers, privacy settings, and non-Chromium clients may not send the requested hint. Keep a conservative default and do not infer a precise device from absence.
  4. Request only necessary detail. Send Accept-CH for named high-entropy fields, review retention, and avoid logging raw values when a coarse category is enough.
  5. Test both surfaces. Verify request headers and navigator.userAgentData in supported contexts, then test the fallback with a synthetic page. Check redirects and cache keys because hints can change which representation a server selects.
DecisionPreferred evidenceSafe fallback
Show a featureRuntime feature detectionAccessible alternate control
Choose a coarse layoutLow-entropy mobile hint plus viewportResponsive CSS
Select a downloadExplicit user choice or capabilityGeneric compatible package
Diagnose a regressionDeclared release and test resultAsk for reproducible steps

Privacy and compatibility boundaries

Client Hints are not a license to collect an identity inventory. High-entropy requests can increase linkability, so tie each field to a user-visible purpose, minimize retention, and keep account, location, and unrelated device data out of compatibility logs. GREASE brand entries are intentionally variable; parsers must accept unknown brands and ordering instead of matching a fixed list.

Treat UA-CH as progressive enhancement. A proxy, cache, embedded document, browser setting, or non-Chromium implementation can change which hints appear. navigator.userAgentData is not available everywhere, and a successful promise does not prove that an application action completed. Separate browser observation, permission outcome, application acknowledgement, and service result in test records.

For background on identity surfaces, see custom User-Agent consistency and browser feature quality. Those articles describe why changing one surface or seeing one supported API is not a complete compatibility claim.

BotBrowser capability boundary

BotBrowser can supply controlled profiles and repeatable, authorized journeys for comparing User-Agent and UA-CH behavior across declared releases. A test can record the request headers, page values, worker values, context assumptions, fixture version, and visible application result. Keep that evidence scoped to the owned test page.

BotBrowser does not make a site accept a hint, grant a permission, remove an origin policy, or certify that a production account or transaction succeeded. Its controlled context also does not justify collecting a full fingerprint. Use BotBrowser observations with W3C and MDN semantics and application-level assertions; state the release, origin, policy, and fallback whenever reporting a result.

Keep parsing tolerant.

UA-CH brand lists are deliberately extensible. A parser should treat the list as data rather than as a string template. Ignore an unrecognized brand, accept a changed order, and compare a major version only when the product decision explicitly needs it. Do not reject a request because a GREASE brand is present. This makes the parser resilient to browser releases and avoids turning an anti-detection design feature into an outage. The same rule applies to future hint names: unknown fields should be ignored, while a missing required field should select the documented fallback.

Separate server and browser responsibilities.

The server sees request headers before JavaScript runs. It can choose a coarse representation, but it cannot know whether a page will later obtain a permission or whether a user will complete a task. The page can test a capability and explain a fallback, but it cannot retroactively change a cache entry selected by the server. Keep the contract between these layers explicit. If a response varies on a hint, send the appropriate Vary metadata and test a warm cache as well as a cold request. A correct hint parser with an incorrect cache key is still a compatibility bug.

Use a synthetic migration fixture.

Create a small page that prints only the fields under review and exposes a visible status for each branch. Run it once with UA-CH available, once with the API unavailable, and once with a requested high-entropy value denied or delayed. The fixture should use synthetic text, no account, and no production endpoint. Assert that the fallback control is visible, that entered text survives the branch, and that focus moves to the status or alternative. This produces evidence about the browser and page contract without retaining an unrelated device inventory.

Check redirects and caches.

Client Hints are request metadata, so redirects can change when a hint is sent and which response establishes Accept-CH. A CDN may also cache a response produced for one hint value and serve it to another context if Vary is incomplete. Test the first navigation, a redirected navigation, and a repeat navigation with a warm cache. Record only the declared hint category, response class, and visible result. Do not log complete request headers when a normalized value answers the compatibility question.

Treat high entropy as an exception.

High-entropy fields should have an owner and an expiry date. A download service may need an architecture category; it rarely needs a full model string. A responsive page normally needs neither, because CSS and viewport APIs express the layout decision more directly. Before adding a field, write the failure that occurs without it, the minimum precision that fixes that failure, and the retention period. If the failure can be solved with a feature test or an explicit user choice, prefer that path over an additional hint.

Non-Chromium and older clients.

UA-CH originated in the Chromium ecosystem, but a product cannot assume that every visitor sends these headers. Firefox, Safari, embedded web views, privacy tools, and older releases may expose only the reduced string or neither signal. The fallback must therefore be a product behavior, not a browser-name guess. A generic download, responsive CSS, or an explicit choice is usually safer than a large table of inferred device models. Keep the compatibility note honest: “the hint was unavailable” is a useful observation, while “the device is unsupported” is a broader claim that needs separate evidence.

Worker and embedded contexts.

Pages can start workers, run inside an iframe, or be restored from a back-forward cache. The JavaScript API and the request headers may be observed in different contexts and at different times. Include one worker check if the application depends on worker code, and one embedded check if the feature is offered in an iframe. A page-level monkey patch is not a standards migration; it can make the main thread disagree with a worker or with the network. Record the context boundary in the fixture and keep the assertion limited to the values the application owns.

Accessibility and user control.

A compatibility fallback is successful only when a person can use it. Label the alternate control, preserve keyboard access, move focus after a denied or missing capability, and expose a concise status message. Do not hide a fallback until a browser promise resolves, because a delayed promise can leave a user without a path forward. If a late result arrives after the user selected the alternative, ignore the stale result. These interaction rules belong to the application and cannot be inferred from a green UA-CH header.

Observability without surveillance.

For an owned test cohort, retain a normalized state such as hint-present, hint-denied, api-missing, or fallback-used. Include the declared browser release, origin class, fixture revision, and next review date. Avoid full navigator dumps, font lists, renderer strings, account identifiers, precise locations, and raw user input. This smaller record is easier to compare after a browser update and reduces the risk that a compatibility check becomes an identity database. A privacy review should be part of the migration pull request, not a later cleanup task.

Release and rollback discipline.

Roll out a parser change behind a reversible configuration. Compare the new decision with the old decision for synthetic requests before changing customer-facing defaults. If the new hint is absent or malformed, route to the established fallback and emit a bounded diagnostic category rather than the raw header. Keep a short rollback path while cache keys, analytics dimensions, and support copy are updated. When a browser release changes a GREASE pattern or a hint name, update the parser tests and the fixture together; do not broaden the accepted identity fields just to make a test pass.

What a good migration record contains.

The record should name the product decision, the exact hint or feature check, the public specification and MDN page consulted, the declared release set, the context assumptions, and the fallback owner. It should say whether the observation came from a request header, page API, worker, or application acknowledgement. Mark unknown values as unknown. A missing server acknowledgement is not evidence that the browser rejected the request, and a permission denial is not evidence that the API is absent. This vocabulary keeps support, engineering, and privacy reviews discussing the same boundary.

Review cadence.

Set a review date when the migration is introduced and revisit it after browser major releases, CDN policy changes, or a new embedded integration. Re-run the synthetic fixture with the same input and compare state transitions, not just a single status code. If only visible copy changes, update the accessibility assertion. If the available hint changes, check the specification and browser notes before changing the support matrix. A small recurring review prevents a once-correct parser from becoming an undocumented dependency on a frozen string.

The migration is also a communication change. Explain to support staff which observation they can request from a customer, and which observations should stay inside an owned test. A support note can say that Sec-CH-UA-Platform was absent, that the page selected its responsive fallback, and that the user could continue. It should not ask for a complete header dump, a device inventory, or an account screenshot. Clear boundaries make troubleshooting faster because the next action is tied to a known owner.

When a framework exposes a convenience option, check what it actually changes. A wrapper may set a string for one page, suppress an exception, or defer a call until a click. None of those behaviors automatically changes request hints or worker scope. Keep one browser-level fixture and one user-level journey. The first identifies whether the signal is available; the second verifies that the application handles the available, missing, denied, and delayed states without losing data. This layered evidence is more durable than a framework version claim.

Finally, document what the migration intentionally does not solve. It does not identify every browser, force a server to honor a header, or make a permission prompt succeed. It does not establish that a user is on a particular operating system when the browser reports a generic platform. It does not turn a controlled BotBrowser run into a cross-browser guarantee. Naming these limits prevents a compatibility note from becoming an accidental promise and gives future maintainers a precise reason to keep the fallback and privacy review in place.

Keep examples representative but non-identifying. A synthetic platform value and a coarse release label are enough to exercise a branch. Avoid copying a real customer header into a fixture, because it can contain account, locale, or network clues that are irrelevant to the test. Review examples when the browser changes its reduced string or adds a hint, and replace stale examples with values from public documentation. The goal is a stable contract that readers can understand, not a catalog of every browser token.

Migration ownership should be visible in the repository. Assign one owner for the server parser, one for the page fallback, and one for privacy review. Record the expected state transition for each owner and the date of the next check. When a failure crosses boundaries, report the boundary rather than assigning blame to the browser: “header absent, responsive fallback shown” is actionable; “browser unsupported” is not precise enough to select a fix. This small vocabulary helps teams compare releases without expanding collection.

Review the note with both engineering and privacy owners.

Document the purpose, minimum signal, retention owner, and condition that triggers another compatibility review so future readers can revisit the decision without creating a permanent identity profile.

Practical conclusion

Start with the user decision, not with a replacement parser. Use feature detection for capability, low-entropy UA-CH for a coarse server decision, and a narrowly justified high-entropy request only when the product cannot work without it. Test absent, denied, delayed, and fallback states; preserve the user's work; and keep browser evidence separate from application success. This migration reduces brittle assumptions while respecting privacy and the limits of what any browser context can prove.

Sources

#User Agent#Client Hints#Ua-Ch#Privacy#Compatibility

Take BotBrowser from research to production

The guides cover the model first, then move into cross-platform validation, isolated contexts, and scale-ready browser deployment.