Skip to content

API Reference ​

Current OS ​

getOS() — returns a single string identifying the current operating system.

ts
import { getOS } from 'os-detect'

const os = getOS()
// 'ios' | 'macos' | 'android' | 'windows' | 'linux' | 'chromeos' | 'unknown'

Detection priority (first match wins):

  1. iOS / iPadOS
  2. Android
  3. ChromeOS
  4. Linux
  5. macOS
  6. Windows
  7. 'unknown'

ChromeOS is checked before Linux because ChromeOS userAgent strings contain "Linux" — checking in the wrong order would misidentify Chromebooks.

OS type ​

ts
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.

ts
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 ​

ts
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.

ts
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.

ts
// 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.

ts
import { resetDetectionCache } from 'os-detect'

resetDetectionCache() // clears every cached detection result

In 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.platform after switching contexts.
  • Test suites that mock navigator/process.platform per 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.

ts
detectIsiOS() // ⚠ deprecated — logs console.warn once per session
detectIsIOS() // ✓ use this instead

Device category ​

Quick coarse checks that group OS values into mobile and desktop buckets.

ts
import { isMobileDevice, isDesktopDevice } from 'os-detect'

isMobileDevice() // true if iOS or Android
isDesktopDevice() // true if macOS, Windows, Linux, or ChromeOS

Note: isMobileDevice() and isDesktopDevice() are not mutually exclusive. A device with an unrecognized OS returns false for 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:

  1. detectIsTV() is true → 'tv'
  2. iOS or Android, screen's shorter dimension >= 768px (the classic tablet breakpoint) → 'tablet', otherwise 'phone'
  3. macOS, Windows, Linux, or ChromeOS → 'desktop'
  4. Anything else, or no screen to measure (Node.js/SSR) → 'unknown'
ts
import { getFormFactor } from 'os-detect'

const formFactor = getFormFactor()

FormFactor type ​

ts
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.

ts
import { detectHasTouch } from 'os-detect'

detectHasTouch() // true on any device with a touchscreen — hybrid laptops included

getPrimaryInput() ​

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.

ts
import { getPrimaryInput } from 'os-detect'

getPrimaryInput() // 'mouse' | 'touch' | 'unknown'

PrimaryInput type ​

ts
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.

ts
import { getPixelRatio } from 'os-detect'

getPixelRatio() // e.g. 2 on a Retina display

detectIsTV() ​

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.

ts
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.

ts
import { getRuntime } from 'os-detect'

getRuntime() // 'browser' in a tab, 'node' in a plain script, 'webworker' inside a Worker

Runtime type ​

ts
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.

ts
import { detectIsNode } from 'os-detect'

detectIsNode() // true anywhere process.versions.node is set

detectIsBrowser() ​

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.

ts
import { detectIsBrowser } from 'os-detect'

detectIsBrowser() // false on the server, true once it hydrates

detectIsWebWorker() ​

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.

ts
import { detectIsWebWorker } from 'os-detect'

detectIsWebWorker() // true only inside a Worker context

detectIsElectron() ​

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.

ts
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.

ts
import { detectIsPWA } from 'os-detect'

detectIsPWA() // true once installed and launched standalone

TypeScript types ​

All public types are exported from the package root:

ts
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() ​

ts
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'.