> For the complete documentation index, see [llms.txt](/llms.txt)

# 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](https://browserless.io/account/)

`emulationOs` is a URL query parameter you add to the endpoint. It works on every stealth and BQL route and on any [regional endpoint](/baas/connection-url-patterns#regional-endpoints) — it is not a header, a launch option, or a mutation.

## 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](#android-mobile-emulation) |

Desktop `windows` and `macos` and mobile `android` identities are supported. iPhone, iOS, and Safari are not. Any unrecognized `emulationOs` value (a typo or wrong casing) is silently ignored and the session runs as native Linux with no error, so verify `navigator.platform` if you're unsure the value took effect.

## 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`, or `Linux 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. It does **not** change:

- **Timezone, locale, and `Accept-Language`** — these follow your [proxy](/baas/bot-detection/proxies)'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.

A Windows 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:**

```js
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();
```
  
  
**Playwright:**

    
      
**Javascript:**

```js
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();
```
      
      
**Python:**

```python
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](/browserql/bot-detection/os-emulation) 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:

```bash
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:

```bash
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](/browserql/bot-detection/os-emulation#android-mobile-emulation).

### Choosing a device

`emulatedDevice` takes one of the supported Android slugs — for example `pixel-8-pro` or `galaxy-s25-ultra`. See [Supported mobile devices](/baas/bot-detection/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

- `emulatedDevice` requires `emulationOs=android`. Sending `emulatedDevice` on a stealth session without it returns an **HTTP 400** with the message: `emulatedDevice requires emulationOs=android on a stealth request (a /stealth/* route or a BQL route).`
- Device slugs are **case-sensitive** and matched exactly. Use them as listed (`pixel-8`, not `Pixel-8` or `pixel8`).
- An unrecognized `emulatedDevice` value is **not** an error. Browserless falls back to an automatically chosen device.
- `emulationOs` accepts `windows`, `macos`, `linux`, and `android`.

### 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](/baas/bot-detection/proxies) — 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. On routes like `/chromium` or `/chrome` the parameter is accepted but no stealth hardening is injected, so the spoofing never takes effect. Use a stealth route (`/stealth`) or a BQL route.

### 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](/baas/bot-detection/supported-mobile-devices).

### 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.)

## Next Steps

  <a href="/baas/bot-detection/stealth" className="next-step-card-link">
    
      <h3 className="next-step-card-title">Stealth Routes</h3>
      Full fingerprint hardening with managed browser configurations.
    
  </a>

  <a href="/baas/bot-detection/user-agent-masking" className="next-step-card-link">
    
      <h3 className="next-step-card-title">User Agent Masking</h3>
      Control your browser's User-Agent string for target site compatibility.
    
  </a>

  <a href="/baas/bot-detection/proxies" className="next-step-card-link">
    
      <h3 className="next-step-card-title">Proxies</h3>
      Add residential proxies for better IP reputation alongside OS emulation.
    
  </a>