OS emulation
Browserless sessions run on Linux, so they expose a Linux fingerprint by default. Bot detection systems cross-check signals like the User-Agent, navigator.platform, UA Client Hints, GPU renderer, and media devices, and flag sessions where these don't line up. The emulationOs query parameter switches the session to a coherent operating system identity — set it to windows or macos to move off the default Linux fingerprint.
- A Browserless API token from your account dashboard
emulationOs is a URL query parameter you add to the endpoint. It works on stealth routes (/stealth, /chromium/stealth, /chrome/stealth), on the unblock and scrape REST APIs (/unblock, /chromium/unblock, /chrome/unblock, /smart-scrape), on BQL routes, and on the Agent route (/chromium/agent), on any regional endpoint. It is not a header, a launch option, or a mutation. Anywhere else, including plain /chromium or /chrome routes, it has no effect: the parameter is ignored and the session runs with its default identity.
Accepted values
| Value | Emulated identity |
|---|---|
windows | Windows / Chrome on Win64 |
macos | macOS / Chrome on Intel Mac |
linux | Native Linux identity (the default when omitted) |
android | Mobile Chrome on Android — see Android (mobile) emulation |
Desktop windows, macos, and linux and mobile android identities are supported. iPhone, iOS, and Safari are not. Values are exact-match and lowercase: any other value, including a typo or wrong casing like macOS, is silently ignored. The session behaves as if emulationOs wasn't set, so double-check the spelling, because there's no error to catch.
What gets spoofed
When emulationOs is set, the session presents one internally consistent set of signals for the target OS:
- User-Agent (e.g.
Windows NT 10.0; Win64; x64) - UA Client Hints —
Sec-CH-UA-Platform,Sec-CH-UA-Platform-Version,getHighEntropyValues() navigator.platform—Win32,MacIntel, orLinux x86_64- GPU / WebGL renderer and CPU core count (
navigator.hardwareConcurrency) for the platform - Media devices, speech-synthesis voices, and AudioContext latency for the OS
These signals stay consistent for the session's lifetime, including across navigations and new windows.
What it doesn't change
emulationOs only sets OS-derived browser signals. What stays untouched depends on the identity.
Desktop identities
For windows, macos, and linux, emulationOs does not change:
- Timezone, locale, and
Accept-Language— these follow your proxy's location, not the emulated OS. - Screen and viewport size — set these on the client (
defaultViewport/setViewport). - WebRTC handling — applied uniformly to every stealth session.
Android emulation
emulationOs=android does set screen and viewport size: it applies a high-DPI mobile viewport (see Android (mobile) emulation). Everything else stays the same as desktop. It does not change:
- Timezone, locale, and
Accept-Language— these follow your proxy's location, not the emulated OS. - WebRTC handling — applied uniformly to every stealth session.
An emulated identity served with a mismatched timezone or locale is itself a detection signal, so pair emulationOs with a region-matched proxy for a fully coherent profile.
Usage with stealth routes
Stealth routes (/stealth, /chromium/stealth, /chrome/stealth) already run the full stealth stack. Adding emulationOs shifts the whole identity to the target OS:
- Puppeteer
- Playwright
import puppeteer from "puppeteer-core";
const TOKEN = "YOUR_API_TOKEN_HERE";
const browser = await puppeteer.connect({
browserWSEndpoint:
`wss://production-sfo.browserless.io/stealth?token=${TOKEN}&emulationOs=windows`,
});
const page = await browser.newPage();
const platform = await page.evaluate(() => navigator.platform);
console.log(platform); // "Win32"
await browser.close();
- Javascript
- Python
import { chromium } from "playwright-core";
const TOKEN = "YOUR_API_TOKEN_HERE";
const browser = await chromium.connectOverCDP(
`wss://production-sfo.browserless.io/stealth?token=${TOKEN}&emulationOs=windows`
);
const context = browser.contexts()[0] || (await browser.newContext());
const page = await context.newPage();
const platform = await page.evaluate(() => navigator.platform);
console.log(platform); // "Win32"
await browser.close();
import asyncio
from playwright.async_api import async_playwright
TOKEN = "YOUR_API_TOKEN_HERE"
async def main():
async with async_playwright() as p:
browser = await p.chromium.connect_over_cdp(
f"wss://production-sfo.browserless.io/stealth?token={TOKEN}&emulationOs=windows"
)
context = browser.contexts[0] if browser.contexts else await browser.new_context()
page = await context.new_page()
platform = await page.evaluate("navigator.platform")
print(platform) # "Win32"
await browser.close()
asyncio.run(main())
With emulationOs=windows or macos, the session's User-Agent is authoritative: any --user-agent you pass in launch options is overridden to keep the identity coherent. To control the User-Agent yourself, omit emulationOs or use BrowserQL's userAgent() mutation.
Usage on BQL routes
BQL routes (/chromium/bql, /chrome/bql, /stealth/bql) accept emulationOs the same way, and all of them run stealth-hardened. See OS emulation in BrowserQL for BQL-specific usage.
Android (mobile) emulation
Set emulationOs=android to make the session present as mobile Chrome on Android instead of a desktop OS. Add emulatedDevice=<slug> to pick a specific phone, or omit it and Browserless picks a real device for you and keeps it consistent for the whole session. Android emulation runs on the Chromium and Brave stealth engines. iPhone, iOS, and Safari are not supported.
Enabling Android emulation
emulationOs and emulatedDevice are connection query-string parameters you add to the stealth connection URL, the same way you add emulationOs=windows for desktop. Add them to a /stealth connection:
wss://production-sfo.browserless.io/stealth?token=YOUR_API_TOKEN_HERE&emulationOs=android&emulatedDevice=pixel-8-pro
Omit emulatedDevice to let Browserless pick a device for you:
wss://production-sfo.browserless.io/stealth?token=YOUR_API_TOKEN_HERE&emulationOs=android
Connect Puppeteer or Playwright to that endpoint and the session runs as normal, presenting the mobile persona. The same parameters also work on BQL routes — see OS emulation in BrowserQL.
Choosing a device
emulatedDevice takes one of the supported Android slugs — for example pixel-8-pro or galaxy-s25-ultra. See Supported mobile devices for the full list. Omitting emulatedDevice is fine: Browserless picks a real device automatically and keeps it stable across reconnects within the session.
What gets emulated
emulationOs=android shifts the whole identity to a coherent mobile persona:
- Mobile User-Agent and Client Hints — the browser reports as Chrome on Android, and the specific model is exposed through UA Client Hints.
- Phone screen size and pixel ratio — a high-DPI mobile viewport.
- Touch input — coarse-pointer and no-hover behavior.
- Portrait orientation.
- Motion sensors — accelerometer, gyroscope, and orientation.
- Mobile GPU / WebGL renderer identity, Android system fonts, mobile media devices and voices, and Android-appropriate battery and network signals.
Rules and errors
emulatedDevicerequiresemulationOs=android. SendingemulatedDeviceon a stealth session without it returns an HTTP 400 with the message:emulatedDevice requires emulationOs=android on a stealth request (a /stealth/*, BQL, or /chromium/agent route).- Device slugs are case-sensitive and matched exactly. Use them as listed (
pixel-8, notPixel-8orpixel8). - An unrecognized
emulatedDevicevalue is not an error. Browserless falls back to an automatically chosen device. emulationOsaccepts only the exact lowercase valueswindows,macos,linux, andandroid. Any other value is silently ignored. A typo shows up as a default-identity fingerprint, not as an error.
Limitations
- Android only — no iPhone, iOS, or Safari.
- All emulated devices are modern flagship phones in portrait orientation.
FAQ & Troubleshooting
I set emulationOs but the site still detects me. What else can I do?
OS emulation is one layer of the stealth stack. For heavily protected sites, pair it with a region-matched residential proxy — which also aligns timezone and locale — and make sure you're on a stealth or BQL route rather than a plain /chromium connection. Some sites also fingerprint TLS and HTTP/2 traits that are independent of the OS identity.
Does emulationOs work on plain (non-stealth) WebSocket routes?
No. emulationOs only takes effect on stealth routes, the unblock and scrape REST APIs (/unblock, /chromium/unblock, /chrome/unblock, /smart-scrape), BQL routes, and the Agent route (/chromium/agent). On a plain route like /chromium or /chrome it is ignored rather than rejected, so the session silently keeps its default identity. Use a stealth route (/stealth), /unblock, /smart-scrape, a BQL route, or /chromium/agent.
Can I emulate an iPhone or iOS?
No. Android is the only mobile persona. There is no iPhone, iOS, or Safari emulation. For mobile testing, use emulationOs=android with an optional device slug.
Can I change emulationOs mid-session?
No. The emulated OS identity is fixed when the session is created and reused for the connection's lifetime, including across reconnects — you don't need to re-append emulationOs to a reconnect URL. (The User-Agent itself can still be replaced mid-session with BrowserQL's userAgent() mutation, but that doesn't change the emulated platform or GPU.)