User-agent parser · Guide
How to Parse a User-Agent String Without Overtrusting It
A User-Agent string is a compatibility label sent by the client, not a trustworthy device identity. Parsing can produce useful analytics categories and work around a verified browser defect, but token names overlap, versions may be reduced, and any client can forge the header. Use a maintained parser, keep the raw value for reprocessing within privacy limits, and prefer feature detection when application behaviour depends on a capability.
Read a User-Agent as an ordered product list
Modern browser strings contain several historical compatibility tokens. A Chrome string mentions Mozilla, AppleWebKit, KHTML, Chrome and Safari; matching the first familiar word labels nearly every browser incorrectly.
Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/128.0.0.0 Safari/537.36The parenthesised comments usually provide platform hints, while product/version tokens identify engines and browsers. Their order and combinations matter. Chromium-based Edge includes Edg/; Opera includes OPR/; both may also contain Chrome/ and must be recognised before the generic Chrome rule.
Do not split only on spaces. Comments contain spaces and semicolons, product tokens may be absent, and bot identifiers use their own conventions. A maintained parser encodes these precedence rules and is easier to update.
Extract browser and engine separately
Browser brand and rendering engine are related but distinct. Chrome, Edge and Opera commonly use Blink; Safari uses WebKit; Firefox uses Gecko. A compatibility decision tied to an engine should not be inferred from one brand token if feature detection can answer it directly.
function roughBrowser(ua) {
if (/Edg\//.test(ua)) return 'Edge';
if (/OPR\//.test(ua)) return 'Opera';
if (/Chrome\//.test(ua)) return 'Chrome';
if (/Firefox\//.test(ua)) return 'Firefox';
if (/Safari\//.test(ua) && /Version\//.test(ua)) return 'Safari';
return 'Unknown';
}
// Illustrates precedence; use a maintained parser in production.Safari's marketed version is normally carried by Version/, not the Safari/ build token. iOS browsers historically include WebKit-related tokens even when their brand differs, so desktop rules cannot simply be reused for mobile.
Store unknown rather than forcing every string into the closest known family. New browsers and privacy-reduced strings otherwise pollute an existing category invisibly.
Interpret operating-system and device hints cautiously
Platform comments can suggest Windows, macOS, Android, iPhone or Linux. They do not reliably identify the physical hardware. Tablets can request desktop sites, compatibility modes can reuse another platform token, and user settings can reduce detail.
# Example derived fields, each nullable
browser.family = Chrome
browser.major = 128
os.family = Windows
device.category = desktop
engine.family = Blink
is_bot = false
# Do not invent missing minor versions or exact device models.Device categories should be broad and nullable. Screen size, touch support, input method and performance cannot be safely derived from a phone word in the header. Responsive layouts should use CSS capabilities and viewport information rather than server-side UA labels.
Architecture tokens are compatibility hints too. A 64-bit process or translated application may report historical identifiers. Do not use them to choose security controls or downloadable binaries without an explicit user choice.
Parse on the server and preserve failure safely
Headers are untrusted input. Limit stored length, remove control characters before logging, and ensure a malformed value produces an unknown result rather than an exception that fails the request. Never interpolate a UA string directly into logs or SQL.
$ua = (string) ($request->header('User-Agent') ?? '');
$ua = substr($ua, 0, 1024);
// Pass $ua to a maintained parser. Treat every returned field as nullable.
$result = [
'browser' => null,
'os' => null,
'device' => null,
];Parser databases change as new products appear. Record the parser version in analytics pipelines and consider retaining a privacy-appropriate raw or hashed source so historical records can be reclassified consistently.
Do not make authentication, authorisation, rate limits or fraud decisions from UA identity alone. Attackers can copy a browser string exactly; it is evidence supplied by the claimant.
Detect bots without assuming certainty
Well-behaved crawlers often identify themselves with tokens such as Googlebot and may include an information URL. Malicious scrapers frequently impersonate ordinary browsers. A regex containing bot|crawler|spider is useful for rough analytics, not an access-control boundary.
def looks_like_bot(user_agent: str) -> bool:
markers = ('bot', 'crawler', 'spider', 'slurp')
value = user_agent.casefold()
return any(marker in value for marker in markers)
# Heuristic only: false positives and false negatives are expected.If verified crawler identity matters, follow that operator's documented reverse- and forward-DNS verification process and cache the result. Do not trust a hostname obtained from reverse DNS without confirming that it resolves back to the connecting IP.
Rate-limit based on behaviour and server-controlled identifiers rather than granting unlimited access to anything claiming to be a bot. Keep monitoring separate from blocking so parser updates do not unexpectedly deny real users.
Use User-Agent Client Hints where appropriate
Supporting browsers may send low-entropy client hints such as Sec-CH-UA and Sec-CH-UA-Mobile. Higher-entropy hints generally require server opt-in with Accept-CH, are subject to browser policy, and may not arrive on the first request.
GET / HTTP/1.1
User-Agent: Mozilla/5.0 ...
Sec-CH-UA: "Chromium";v="128", "Not;A=Brand";v="99"
Sec-CH-UA-Mobile: ?0
Sec-CH-UA-Platform: "Windows"Brand lists can contain intentionally unusual entries, so do not parse the header by splitting on commas and assuming the first brand is the browser. Use a structured-fields-aware implementation and handle missing hints.
Client Hints complement rather than universally replace the User-Agent. Design a fallback, minimise requested detail, and add the relevant Vary headers when response content truly changes by a hint to avoid cache contamination.
Choose feature detection over browser detection
When code needs to know whether a browser supports a capability, ask the runtime. Property checks, CSS @supports and progressive enhancement survive new browsers and UA reduction far better than a list of version cut-offs.
if ('showOpenFilePicker' in window) {
enableNativeFilePicker();
} else {
enableUploadInputFallback();
}
// No browser name or version table required.UA parsing remains reasonable for aggregate analytics, debugging reports and narrowly targeted workarounds for confirmed defects. For a workaround, combine the smallest UA condition with a capability or behaviour test and remove it when affected versions age out.
Test empty, truncated, forged and future-looking strings. Consumers must accept nullable fields, and analytics should distinguish unknown from missing so parser gaps are visible rather than silently counted as another browser.
Frequently asked questions
How do I parse a User-Agent string?
Pass the complete header to a maintained UA parser and read nullable browser, engine, operating-system and device fields. Avoid matching the first familiar token because Chromium-family strings contain several compatibility product names.
Can I detect a browser from User-Agent reliably?
Only approximately. Browsers reduce or freeze some values, compatibility tokens overlap, and clients can forge the header. Use parsing for analytics or narrow workarounds, not as a security identity.
Why does Chrome's User-Agent contain Safari?
The Safari/ token is retained for web compatibility and does not mean the browser is Safari. Detection rules must recognise more specific Edge, Opera and Chrome tokens before a Safari rule, or use a maintained parser.
Can a User-Agent identify a mobile device?
It can provide a broad hint, but desktop mode, tablets and reduced strings make it uncertain. Build responsive interfaces from viewport and capability information rather than treating the UA category as physical truth.
How can I detect bots from User-Agent?
Bot-name matching is a heuristic suitable for rough analytics. Any scraper can claim a normal browser; verify important crawlers using their documented IP/DNS process and base abuse controls on behaviour.
Should I use User-Agent Client Hints instead?
Use them when supported and when the extra detail is necessary, with a missing-data fallback. Parse the structured brand list correctly, request minimal entropy, and vary caches whenever hints change the response.
Ready to try it?
Open the free browser-based User-agent parser and apply what you just read — no sign-up, runs locally.
Open the User-agent parser tool