API Reference
Current OS
getOS() — returns a single string identifying the current operating system.
import { getOS } from 'os-detect'
const os = getOS()
// 'ios' | 'macos' | 'android' | 'windows' | 'linux' | 'chromeos' | 'unknown'Detection priority (first match wins):
- iOS / iPadOS
- Android
- ChromeOS
- Linux
- macOS
- Windows
'unknown'
ChromeOS is checked before Linux because ChromeOS userAgent strings contain "Linux" — checking in the wrong order would misidentify Chromebooks.
OS type
type OS = 'ios' | 'macos' | 'android' | 'windows' | 'linux' | 'chromeos' | 'unknown'Boolean detection
All functions below are synchronous, side-effect-free, and cache their result after the first call.
import {
detectIsIOS,
detectIsMacOS,
detectIsAndroid,
detectIsWindows,
detectIsLinux,
detectIsChromeOS,
} from 'os-detect'detectIsIOS()
boolean
true for iPhone, iPod, or iPad (including iPadOS 13+). Always false in Node.js — not detectable server-side.
detectIsMacOS()
boolean
true for macOS desktop (correctly excludes iPadOS 13+). In Node.js: process.platform === 'darwin'.
detectIsAndroid()
boolean
true for Android phones and tablets. In Node.js: process.platform === 'android'.
detectIsWindows()
boolean
true for Windows desktop or laptop (32-bit and 64-bit). In Node.js: process.platform === 'win32'.
detectIsLinux()
boolean
true for Linux desktop (excludes Android and ChromeOS). In Node.js: process.platform === 'linux'.
detectIsChromeOS()
boolean
true for ChromeOS devices. Always false in Node.js — not detectable server-side.
iPadOS 13+ note
Since iPadOS 13, Safari reports Macintosh in the userAgent string. detectIsIOS() handles this correctly by checking navigator.maxTouchPoints > 1. detectIsMacOS() excludes iPadOS devices accordingly — you will never get true from both functions on the same device.
Example — conditional rendering
import { detectIsIOS, detectIsAndroid, detectIsWindows } from 'os-detect'
if (detectIsIOS()) {
// show App Store badge
} else if (detectIsAndroid()) {
// show Google Play badge
} else if (detectIsWindows()) {
// show Windows Store badge
}Windows 11 Detection
detectIsWindows11()
Promise<boolean>
true only when both detectIsWindows() is true and the Windows 11 check succeeds.
import { detectIsWindows11 } from 'os-detect'
const isWin11 = await detectIsWindows11()Browser — uses navigator.userAgentData.getHighEntropyValues(['platformVersion']) (Chrome 90+ / Edge 90+). Windows 11 reports platformVersion >= 13.0.0. Returns false if the API is unavailable or the call fails.
Node.js — uses os.release(). Windows 11 has build number >= 22000. Returns false if the import fails.
Returns false immediately if detectIsWindows() is false — no async work is done. Like every boolean detector, the result is cached after the first call; concurrent calls made before it resolves share the same in-flight promise instead of each triggering their own check. Call resetDetectionCache() to force it to re-run.
// Differentiate Windows 10 from Windows 11
const isWindows = detectIsWindows()
const isWin11 = await detectIsWindows11()
if (isWin11) {
console.log('Windows 11')
} else if (isWindows) {
console.log('Windows 10 or older')
}Resetting the Cache
resetDetectionCache()
void
Clears every cached detection result — all detectIs*() functions, getOS(), and detectIsWindows11() re-run their check on the next call. It also resets the detectIsiOS() deprecation-warning flag, so the next call to that alias logs console.warn again.
import { resetDetectionCache } from 'os-detect'
resetDetectionCache() // clears every cached detection resultIn a normal browser tab the OS never changes mid-session, so this is rarely needed. It exists for advanced cases:
- A long-running Node.js process (e.g. an Electron main process) that needs to re-evaluate
process.platformafter switching contexts. - Test suites that mock
navigator/process.platformper test without re-importing the module.
Deprecated Alias
detectIsiOS()
boolean · identical to detectIsIOS()
Deprecated — renamed to detectIsIOS() (capital OS) for consistency with the rest of the API in v2.0. Still works, but logs a console.warn deprecation notice once per session (as of v2.2.1 — call resetDetectionCache() to make it warn again) and will be removed in v3.0. See Migration for the full v1→v2 changelog.
detectIsiOS() // ⚠ deprecated — logs console.warn once per session
detectIsIOS() // ✓ use this insteadDevice category
Quick coarse checks that group OS values into mobile and desktop buckets.
import { isMobileDevice, isDesktopDevice } from 'os-detect'
isMobileDevice() // true if iOS or Android
isDesktopDevice() // true if macOS, Windows, Linux, or ChromeOSNote:
isMobileDevice()andisDesktopDevice()are not mutually exclusive. A device with an unrecognized OS returnsfalsefor both. There is no overlap on the currently recognised OS values.
Form factor & input
getFormFactor()
FormFactor — 'phone' | 'tablet' | 'desktop' | 'tv' | 'unknown'
Driven by OS and physical screen size — not touch capability, so a touchscreen Windows laptop still comes back 'desktop', never 'tablet'. Resolution order:
detectIsTV()istrue→'tv'- iOS or Android, screen's shorter dimension
>= 768px(the classic tablet breakpoint) →'tablet', otherwise'phone' - macOS, Windows, Linux, or ChromeOS →
'desktop' - Anything else, or no
screento measure (Node.js/SSR) →'unknown'
import { getFormFactor } from 'os-detect'
const formFactor = getFormFactor()FormFactor type
type FormFactor = 'phone' | 'tablet' | 'desktop' | 'tv' | 'unknown'detectHasTouch()
boolean
true if the device has a touchscreen at all, independent of which input type is currently primary. Always false in Node.js. Checks navigator.maxTouchPoints first, falling back to the legacy navigator.msMaxTouchPoints and then 'ontouchstart' in window.
import { detectHasTouch } from 'os-detect'
detectHasTouch() // true on any device with a touchscreen — hybrid laptops includedgetPrimaryInput()
PrimaryInput — 'mouse' | 'touch' | 'unknown'
Not cached — unlike every other function on this page, getPrimaryInput() re-reads the pointer media feature on every call, since which input type is primary can genuinely change mid-session (a hybrid device's keyboard/mouse being attached or detached). Reads window.matchMedia('(pointer: fine)') and (pointer: coarse); returns 'unknown' when matchMedia isn't available (older browsers, Node.js/SSR) or neither query matches. Use usePrimaryInput() from os-detect/vue or os-detect/react to react to that live instead of polling this directly.
import { getPrimaryInput } from 'os-detect'
getPrimaryInput() // 'mouse' | 'touch' | 'unknown'PrimaryInput type
type PrimaryInput = 'mouse' | 'touch' | 'unknown'getPixelRatio()
number — window.devicePixelRatio, or 1 in Node.js/SSR
Not cached — cheap to read live, and it can change on desktop when a window moves between monitors with different display scaling.
import { getPixelRatio } from 'os-detect'
getPixelRatio() // e.g. 2 on a Retina displaydetectIsTV()
boolean
Best-effort match against known smart TV platforms (Tizen, webOS, Android TV/Google TV, HbbTV, Fire TV, Sony BRAVIA, VIDAA). Always false in Node.js.
import { detectIsTV } from 'os-detect'
detectIsTV() // true on a Samsung Tizen TV, LG webOS TV, Android TV, etc.Note: UA-based TV detection is inherently best-effort — some TV browsers ship a near-generic Android/Chrome userAgent with no reliable TV-specific marker at all, so this can under-detect on those. It's designed to never over-detect a phone, tablet, or desktop as a TV.
Runtime context
getRuntime()
Runtime — 'node' | 'browser' | 'webworker' | 'unknown'
Checked in this order: detectIsWebWorker() → detectIsBrowser() → detectIsNode(). 'browser' wins over 'node' when both a window and a process are present — an Electron/NW.js renderer with Node integration enabled — see detectIsElectron() below to tell that case apart from a plain browser tab.
import { getRuntime } from 'os-detect'
getRuntime() // 'browser' in a tab, 'node' in a plain script, 'webworker' inside a WorkerRuntime type
type Runtime = 'node' | 'browser' | 'webworker' | 'unknown'detectIsNode()
boolean
true in a plain Node.js script, an Electron main process, or an Electron/NW.js renderer with Node integration enabled. Reads process.versions.node directly, so it stays accurate even when a browser-like navigator is also present.
import { detectIsNode } from 'os-detect'
detectIsNode() // true anywhere process.versions.node is setdetectIsBrowser()
boolean
true when both window and document exist: a real browser tab, or an Electron/NW.js renderer. false in Node.js, inside a Web Worker, and during SSR.
import { detectIsBrowser } from 'os-detect'
detectIsBrowser() // false on the server, true once it hydratesdetectIsWebWorker()
boolean
true inside a Dedicated, Shared, or Service Worker. Checks for self without window, plus importScripts (defined on WorkerGlobalScope, which all three worker types inherit from) — self without window alone isn't enough, since plain Node.js has neither.
import { detectIsWebWorker } from 'os-detect'
detectIsWebWorker() // true only inside a Worker contextdetectIsElectron()
boolean
true in Electron's main process, or a renderer process with or without Node integration enabled. Checks process.versions.electron first, falling back to an Electron/ token in navigator.userAgent for a sandboxed/contextIsolated renderer where process isn't exposed to page JS at all.
import { getRuntime, detectIsElectron } from 'os-detect'
if (getRuntime() === 'browser' && detectIsElectron()) {
console.log('Running inside an Electron renderer')
}detectIsPWA()
boolean
true when the app is running installed, in its own standalone window, rather than a regular browser tab. Always false in Node.js. Checks, in order: the display-mode: standalone and display-mode: window-controls-overlay media features, iOS Safari's legacy navigator.standalone flag, and a Trusted Web Activity's android-app:// document referrer.
import { detectIsPWA } from 'os-detect'
detectIsPWA() // true once installed and launched standaloneTypeScript types
All public types are exported from the package root:
import type { OS, FormFactor, Runtime, PrimaryInput } from 'os-detect'
// OS: 'ios' | 'macos' | 'android' | 'windows' | 'linux' | 'chromeos' | 'unknown'
// FormFactor: 'phone' | 'tablet' | 'desktop' | 'tv' | 'unknown'
// Runtime: 'node' | 'browser' | 'webworker' | 'unknown'
// PrimaryInput: 'mouse' | 'touch' | 'unknown'Narrowing with getOS()
import { getOS } from 'os-detect'
import type { OS } from 'os-detect'
const os: OS = getOS()
switch (os) {
case 'ios':
case 'android':
console.log('Mobile device')
break
case 'windows':
case 'macos':
case 'linux':
case 'chromeos':
console.log('Desktop device')
break
case 'unknown':
console.log('Unrecognised OS')
break
}TypeScript knows all branches are exhausted after the switch — no need for a default case if you handle 'unknown'.