Skip to content
Engineering · 5 min read

Feature-detect the browser, never sniff the user agent

The claim Deciding what your web application does based on which browser it thinks the visitor is using — reading the user-agent string and branching on it — is a technique that ha...

A Written by Administrator
Feature-detect the browser, never sniff the user agent

The claim

Deciding what your web application does based on which browser it thinks the visitor is using — reading the user-agent string and branching on it — is a technique that has been breaking websites for two decades and continues to. The correct approach is to ask whether the specific capability you need is present, not who the browser claims to be. Feature detection is more reliable, ages better, and does not punish users on browsers you did not think to test, and it is not meaningfully more work than the fragile alternative.

Why user-agent sniffing is a trap

The user-agent string is a self-reported label, and branching your behaviour on it fails in several ways at once. It is a moving target: browsers update constantly, new ones appear, and a check that recognises today's versions silently mishandles tomorrow's. It is routinely spoofed and altered, by browsers imitating each other for compatibility and by privacy tools that deliberately change it, so the string frequently does not describe the browser accurately even when you parse it correctly. And it invites the specific failure of serving a degraded experience to a browser you did not test — your code does not recognise the string, falls through to a fallback, and a perfectly capable browser gets the broken path because it was unfamiliar rather than incapable.

The historical evidence is overwhelming. The reason browser user-agent strings are the tangled mess they are today — every browser claiming to be several others — is precisely that decades of sites sniffed them and served the wrong thing, forcing each new browser to lie about its identity just to receive the working code path. Sniffing created the very mess that makes sniffing unreliable.

Ask about the capability, not the identity

Feature detection tests directly for the thing you actually care about: is the capability I am about to use available? The browser's name never enters into it.

// wrong: branch on who the browser claims to be
if (navigator.userAgent.includes('Safari')) { /* fragile, wrong tomorrow */ }

// right: ask whether the capability exists
if ('IntersectionObserver' in window) {
  useIntersectionObserver();
} else {
  useScrollFallback();
}

This is correct regardless of which browser is visiting, including browsers that did not exist when you wrote the code. A new browser that supports the feature takes the good path automatically; one that does not takes the fallback automatically; and neither depends on your code recognising a name. The test is about the capability, which is the thing that actually determines whether your code will work.

Build up from a baseline that works everywhere

The strongest application of this principle is progressive enhancement: start with a version that works using only capabilities every browser has, then layer on enhancements guarded by feature detection. The core experience — reading the content, submitting the form, completing the purchase — depends on nothing exotic and therefore works everywhere. The enhancements — the smoother interaction, the richer control — activate only where their required capability is present.

// the form works with a plain submit everywhere.
// enhance it only where the needed API exists:
if ('fetch' in window) {
  form.addEventListener('submit', submitWithoutReload);
}

Built this way, a browser missing a capability does not get a broken experience — it gets the baseline experience, which is fully functional. You have made the enhancement optional rather than the functionality conditional, which means the failure mode of an unrecognised or limited browser is "slightly less polished", not "broken".

The narrow, legitimate uses of the user agent

There are a few honest uses for the user-agent string, and it is worth being precise about them so the rule does not get misread as never touching it. Aggregate analytics — understanding roughly what mix of browsers your visitors use, in order to decide what to test — is a reasonable use, because it informs your testing rather than branching your behaviour. Working around a specific, documented bug in a specific browser version, as a temporary measure with a plan to remove it, is defensible when there is genuinely no feature to detect. What is not legitimate is the general pattern of deciding what features to offer based on browser identity, because that is the pattern that breaks as the browser landscape changes underneath it.

The rule to hold

When you find yourself about to read the user-agent string to decide what your application should do, stop and ask what capability you are really testing for, then test for that capability instead. Your code becomes correct for browsers you have never heard of, stops serving broken experiences to users whose only sin was using something unfamiliar, and stops contributing to the identity-spoofing mess that made sniffing unreliable in the first place. The browser's name is not the information you need; the presence of the capability is, and you can ask for that directly.

#frontend #web development #compatibility #javascript

Keep reading