To detect the user's browser in JavaScript, read navigator.userAgent and match it against each browser's token, in the right order. This guide gives you a tested function that returns the browser name and version, shows how to detect Safari correctly, and explains when you should check for features instead.
import { detectBrowser } from "./detect-browser.js";
const browser = detectBrowser();
console.log(browser);
// In Chrome 156 on Windows:
// { name: "Chrome", version: "156.0.8078.4", majorVersion: 156, mobile: false, iOS: false }Tested on: 78 user-agent strings (the 67 in Playwright's device list plus 11 documented formats for Opera, Samsung Internet, iPhone browsers, Firefox for Android and Internet Explorer), and in real Chromium 156, Chromium 152 and WebKit 27.2 (Safari's engine), October 2026. Firefox was tested through its user-agent strings.
Should You Detect the Browser at All?
Often not. If you want to know whether a feature works, check for the feature itself (see Feature Detection below): that keeps working when browsers add support, and it can't be fooled by a changed user agent. Browser detection is the right tool when you need the name itself:
- analytics and logging (which browsers your users have);
- working around a known bug in a specific browser version;
- showing browser-specific instructions, such as how to install your app on an iPhone or allow notifications;
- support messages like "please update Safari".
Never use it for security. Users and extensions can change the user agent to anything.
What a User-Agent String Looks Like
Every browser sends a user-agent string, and most of it is there for historical compatibility. Almost all of them start with Mozilla/5.0 and most contain Safari:
# Chrome on Windows
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36
# Edge on Windows: the same, plus "Edg/"
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36 Edg/156.0.8078.4
# Firefox on Windows
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:157.0) Gecko/20100101 Firefox/157.0
# Safari on macOS
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/27.2 Safari/605.1.15
# Chrome on iPhone: "CriOS", and no "Chrome"
Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/126.0.6478.54 Mobile/15E148 Safari/604.1So you can't just search for a browser's name. Edge contains Chrome, Chrome contains Safari, and Chrome on iPhone doesn't contain Chrome at all. What works is checking the most specific tokens first.
Detect the Browser Name and Version
// Order matters: Edge, Opera and Samsung Internet also say "Chrome",
// and almost every browser says "Safari".
const BROWSERS = [
{ name: "Edge", pattern: /Edg(?:e|A|iOS)?\/([\d.]+)/ },
{ name: "Opera", pattern: /(?:OPR|OPT)\/([\d.]+)/ },
{ name: "Samsung Internet", pattern: /SamsungBrowser\/([\d.]+)/ },
{ name: "Firefox", pattern: /(?:Firefox|FxiOS)\/([\d.]+)/ },
{ name: "Chrome", pattern: /(?:Chrome|CriOS)\/([\d.]+)/ },
{ name: "Safari", pattern: /Version\/([\d.]+).*Safari\// },
{ name: "Internet Explorer", pattern: /(?:MSIE |Trident\/.*rv:)([\d.]+)/ },
];
export function detectBrowser(userAgent = navigator.userAgent) {
for (const { name, pattern } of BROWSERS) {
const match = userAgent.match(pattern);
if (match) {
return {
name,
version: match[1],
majorVersion: parseInt(match[1], 10),
mobile: /Mobi|Android|iPhone|iPad/.test(userAgent),
// On iPhone and iPad every browser runs on Safari's engine (WebKit).
iOS: /iPhone|iPad|iPod/.test(userAgent),
};
}
}
return { name: "Unknown", version: "", majorVersion: NaN, mobile: false, iOS: false };
}- Order is everything. Edge (Edg/, EdgA/ on Android, EdgiOS/ on iPhone), Opera (OPR/) and Samsung Internet are checked before Chrome because their strings also contain Chrome/. Safari comes last of the modern browsers because nearly everything says Safari/.
- iPhone browsers have their own tokens: CriOS for Chrome, FxiOS for Firefox and EdgiOS for Edge.
- Safari's version is in Version/, not in Safari/ (that number is the WebKit build).
- One answer: the function returns a single name, plus the version as a string and as a number for comparisons.
- Browsers it doesn't know return "Unknown". In our test that happened for two very old devices, a 2013 Kindle Fire and a Nokia N9, which is the honest answer.
All 78 user agents in our test came back with the right name and version. Pass any string to test it, for example detectBrowser(someUserAgent), which also makes it usable on the server.
Why Simple includes() Checks Fail
Many tutorials, including an earlier version of this article, use checks like these:
// Common pattern, including an earlier version of this article. Don't use it.
const isChrome = ua.includes("Chrome") && !ua.includes("OPR");
const isFirefox = ua.includes("Firefox");
const isSafari = ua.includes("Safari") && !ua.includes("Chrome");
const isIE = ua.includes("MSIE") || ua.includes("rv:"); // Firefox has "rv:" too
const isOpera = ua.includes("OPR");We ran this code on the same 78 user agents and it was wrong or ambiguous for 14 of them:
| Browser | Old checks returned | Why |
|---|---|---|
| Firefox (Windows, Android) | firefox + ie | Firefox's user agent contains "rv:" |
| Microsoft Edge | chrome | Edge's user agent contains "Chrome" |
| Samsung Internet | chrome | Its user agent contains "Chrome" |
| Chrome on iPhone | safari | It says "CriOS", not "Chrome" |
| Firefox and Edge on iPhone | safari | "FxiOS" / "EdgiOS" weren't checked |
How to Detect Safari
"Is this Safari?" usually means one of two things, so decide which you need:
const { name, iOS } = detectBrowser();
const isSafari = name === "Safari"; // Apple's Safari app, on Mac, iPhone or iPad
const usesSafariEngine = isSafari || iOS; // also Chrome, Firefox, Edge on iPhone/iPad- isSafari is true only for Apple's Safari. It's the right check for analytics or Safari-specific instructions.
- On iPhone and iPad, Chrome, Firefox and Edge all use Safari's engine (WebKit). If you're working around a Safari rendering or JavaScript bug, it affects those browsers too, so check usesSafariEngine.
- iPads in desktop mode send a Mac user agent, so an iPad can show up as iOS: false. If touch matters, also check navigator.maxTouchPoints > 1.
Check the Browser Version
Compare majorVersion, which is a number:
const { name, majorVersion } = detectBrowser();
if (name === "Safari" && majorVersion < 17) {
showNotice("Please update Safari to use this feature.");
}Chromium browsers now freeze parts of the user agent. For example, Windows 11 still reports Windows NT 10.0. If you need precise OS or full version details in Chromium, use Client Hints.
User-Agent Client Hints (Chromium Only)
navigator.userAgentData is a structured replacement for the user-agent string. Chrome, Edge, Opera and Samsung Internet support it; Firefox and Safari don't, so use it with a fallback:
// User-Agent Client Hints: Chromium browsers only (Chrome, Edge, Opera, Samsung Internet).
// Firefox and Safari don't have navigator.userAgentData, so always keep a fallback.
export async function getBrowserFromClientHints() {
const uaData = navigator.userAgentData;
if (!uaData) return null;
// Brands include a deliberately fake entry like "Not.A/Brand"; skip it
// and prefer a specific browser over the generic "Chromium".
const real = uaData.brands.filter((b) => !/Not.?A.?Brand/i.test(b.brand));
const brand = real.find((b) => b.brand !== "Chromium") ?? real[0];
// Low-entropy values are available right away.
const result = {
name: brand?.brand ?? "Unknown",
majorVersion: Number(brand?.version),
mobile: uaData.mobile,
platform: uaData.platform,
};
// The full version and OS version need an async request.
const high = await uaData.getHighEntropyValues(["fullVersionList", "platformVersion"]);
result.version = high.fullVersionList?.find((b) => b.brand === result.name)?.version ?? "";
result.platformVersion = high.platformVersion;
return result;
}const info = (await getBrowserFromClientHints()) ?? detectBrowser();
console.log(info.name, info.version);- Brands list the browser and its engine, for example Microsoft Edge and Chromium, plus a deliberately fake brand such as Not?A_Brand that stops sites from depending on exact lists. The function skips the fake brand and prefers the specific browser over "Chromium". Apps built on Chromium that don't add their own brand report just "Chromium"; ours did.
- High-entropy values (full version, OS version, device model) need getHighEntropyValues(), which returns a promise and only works on HTTPS or localhost.
- Windows 11: the user agent can't tell it apart from Windows 10, but platformVersion can: 13 or higher means Windows 11, per Microsoft's guide. On our Windows 10 test machine it was 10.0.0.
In WebKit, navigator.userAgentData was undefined, the function returned null, and detectBrowser() gave Safari 27.2. See MDN's Client Hints reference for every field.
Feature Detection: Usually the Better Choice
If the question is "can I use X?", ask the browser directly:
// Check for the feature you need instead of guessing from the browser name.
const supports = {
share: "share" in navigator,
clipboard: !!navigator.clipboard?.writeText,
serviceWorker: "serviceWorker" in navigator,
viewTransitions: "startViewTransition" in document,
cssHas: CSS.supports("selector(:has(a))"),
containerQueries: CSS.supports("container-type: inline-size"),
};
const shareButton = document.querySelector("#share"); // <button id="share" hidden>Share</button>
if (supports.share) {
shareButton.hidden = false;
shareButton.addEventListener("click", () =>
navigator.share({ title: document.title, url: location.href }),
);
}| Instead of | Check |
|---|---|
| "Is it Safari? Then no Web Share" | "share" in navigator |
| "Is it old Chrome? Then no :has()" | CSS.supports("selector(:has(a))") |
| "Is it Firefox? Then no View Transitions" | "startViewTransition" in document |
| "Is it IE?" | Check the exact API you're about to call |
In CSS, @supports does the same job without any JavaScript. For handling page visibility instead of browser type, see our guide to detecting tab changes in JavaScript.
Detecting the Browser on the Server
Because detectBrowser() takes the string as a parameter, you can run it on the request's User-Agent header:
// Next.js route handler: the same function works on the User-Agent header
import { detectBrowser } from "@/lib/detect-browser";
export async function GET(request) {
const browser = detectBrowser(request.headers.get("user-agent") ?? "");
return Response.json(browser);
}Server-side detection is useful for logs and analytics. Be careful about sending different HTML to different browsers: if a CDN caches the page, add Vary: User-Agent or one browser's version can be served to everyone.
Brave, Headless Browsers and Other Special Cases
- Brave sends exactly the same user agent as Chrome on purpose, so detectBrowser() reports Chrome. Brave exposes navigator.brave.isBrave() if you need to know.
- Headless Chrome (used by Playwright, Puppeteer and many bots) says HeadlessChrome/156…, which the function reports as Chrome. Don't rely on that to spot bots; most change their user agent.
- Android WebView (in-app browsers) contains ; wv) and Version/4.0 and is reported as Chrome.
- Internet Explorer is detected (MSIE or Trident/), but Microsoft ended its support in 2022, so you rarely need a special case for it.
Should You Use a Library?
For analytics with many browsers and operating systems, a maintained parser saves time. Bowser (MIT licence, version 2.14 in October 2026) is small and has version ranges built in:
import Bowser from "bowser";
const browser = Bowser.getParser(navigator.userAgent);
browser.getBrowserName(); // e.g. "Microsoft Edge"
browser.getBrowserVersion(); // e.g. "156.0.8078.4"
browser.getOSName(); // e.g. "Windows"
browser.satisfies({ chrome: ">=120", edge: ">=120", safari: ">=17" }); // true or falseThe comments show what Bowser 2.14.1 returned for Edge 156 in our test. UAParser.js is another popular option, but check the licence: version 1 is MIT, while version 2 is AGPL-3.0 (or commercial), which matters for closed-source apps.
Common Mistakes
| Mistake | Result | Fix |
|---|---|---|
| Checking "Chrome" before "Edg" or "OPR" | Edge and Opera reported as Chrome | Check the most specific tokens first |
| Using "rv:" to find Internet Explorer | Every Firefox is also "IE" | Match MSIE or Trident/ |
| Treating every "Safari" as Safari | Chrome and Edge on iPhone reported as Safari | Check CriOS, FxiOS and EdgiOS first |
| Reading Safari's version from "Safari/" | You get the WebKit build number | Read "Version/" |
| Using browser checks for features | Breaks when browsers add support | Feature detection or @supports |
| Trusting the user agent for security | Anyone can change it | Check permissions on the server |
For the full background, MDN's browser detection guide lists the tokens each browser uses.
FAQs
How do I detect the browser in JavaScript?
Read navigator.userAgent and test it with regular expressions in a fixed order: Edge, Opera, Samsung Internet, Firefox, Chrome, then Safari. The detectBrowser() function above returns the name and version.
How do I detect Safari with JavaScript?
Check that the user agent has Version/…Safari/ and none of the other browsers' tokens (detectBrowser().name === "Safari"). A plain includes("Safari") is true for Chrome and Edge as well.
How do I get the browser version in JavaScript?
Use the number after the browser's own token: Chrome/, Firefox/, Edg/, or Version/ for Safari. In Chromium browsers, navigator.userAgentData.getHighEntropyValues(["fullVersionList"]) gives the full version too.
Is navigator.userAgent deprecated?
No, but Chromium browsers freeze parts of it (such as the OS version) for privacy, and Client Hints are the detailed alternative. Firefox and Safari still rely on the user-agent string.
Can I detect Chrome on iPhone?
Yes: its user agent has CriOS/ instead of Chrome/. Remember that it uses Safari's engine, so it has Safari's capabilities and bugs.
How can I tell Brave from Chrome?
Not from the user agent, which is identical. Check navigator.brave, which only Brave provides.
Should I detect the browser or the feature?
Detect the feature whenever you're deciding whether something will work. Detect the browser only when you need its name, such as for analytics, instructions or a version-specific bug fix.
Related Articles
View allAbout Muhaymin Bin Mehmood
Front-end Developer skilled in the MERN stack, experienced in web and mobile development. Proficient in React.js, Node.js, and Express.js, with a focus on client interactions, sales support, and high-performance applications.




