# detectIncognito: asking the browser whether it is in private mode

> detectIncognito is a small TypeScript library that works out whether the visitor is in a private browsing window, by probing each browser for a measurable side effect of private mode rather than by asking. It covers Chrome, Safari on macOS and iOS, Firefox, Edge, Internet Explorer, Brave and Opera, returns the browser name along with the answer, and ships as an ES module, a bundled CDN file and typed definitions.

**Joe12387/detectIncognito** — JavaScript detection of Incognito & other private browsing modes on Chrome, Edge, Safari, Brave, Firefox, Opera and MSIE.

- Repository: https://github.com/Joe12387/detectIncognito
- Stars: 556 · Forks: 57
- Language: TypeScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/joe12387-detectincognito

## What it does, and the browser list

detectIncognito is described as efficiently detecting incognito mode and other private browsing modes across most modern browsers, and the feature list names eight cases.

There is incognito detection on Google Chrome. There is private window detection on Safari on macOS and private tab detection on Safari on iOS, which are separate entries because the two platforms behave differently. There is private window detection in Firefox, InPrivate window detection on Microsoft Edge, and InPrivate window detection on Microsoft Internet Explorer. And there is private window detection in Brave, with a note, and in Opera.

The supported browser table puts versions against that list. Safari on all platforms up to 26.5. Chromium on all platforms from 83 to 150. Firefox on all platforms from 44 to 153. And MSIE on Windows, version 11 only, with the note that it requires a Promise polyfill.

That version table is the most useful thing in the README, because it says exactly where the library stops working. The lower bounds are the interesting direction: Chromium 83 and Firefox 44 are old enough that the technique has had time to change, and the upper bounds are current, so the library is being kept working as the browsers move.

The primary language is TypeScript, the licence is MIT, the default branch is main, and the recent releases are v1.9.0 from 2026-08-09, v1.8.1 from the same day and v1.8.0 from 2026-07-23. The last push was on 2026-08-09 with the project not archived.

The npm package is detectincognitojs, and there is a live demo at detectincognito.com, which is the right place to check the current behaviour across browsers before you rely on it.

## How to use it, and the two delivery routes

There are two ways to get the script, and the notes explain why you might want one over the other.

The first is a CDN, from jsDelivr, pointing at the built es5 bundle in the repository's dist directory:

```html
<script src="https://cdn.jsdelivr.net/gh/Joe12387/detectIncognito@main/dist/es5/detectIncognito.min.js"></script>
```

The second is npm:

```bash
npm i detectincognitojs
```

And the usage is an import and a promise:

```javascript
import { detectIncognito } from "detectincognitojs";

detectIncognito().then((result) => {
  console.log(result.browserName, result.isPrivate);
});
```

The API shape is worth a moment. The function returns a promise, and the result carries two things: the browser name and a boolean for whether it is private. Returning the browser name as well as the answer is a deliberate design choice, and it matters more than it first appears, because the detection techniques differ per browser. A library that only returned a boolean would have to hide which technique it used, and you would lose the ability to reason about a false result.

That also explains the note that an error is thrown if the browser can't be identified. The library does not guess. If it cannot work out which browser it is in, it says so rather than returning a default.

The CDN route comes with a warning, and the warning is the interesting part of the notes: the script may be blocked by adblockers, and Brave and uBlock Origin block the CDN specifically. Hosting the script yourself avoids the issue.

That is a good example of a real deployment problem rather than a theoretical one. A privacy-detection library is exactly the kind of thing an adblocker might block, because a request to a CDN for a script with a fingerprinting-adjacent name looks like tracking. Brave blocks it outright, so on that browser the CDN route simply does not work and you need to bundle it yourself.

The other notes cover the remaining edge cases. False positives can occur in certain browser setups or in Chrome Guest mode, tracked as issue 21. Firefox Container Tabs are not detected. And the script must run over HTTPS, because running locally or over HTTP might fail.

That last one is the kind of constraint that will cost you an hour if you hit it in development. Several of the detection techniques depend on browser behaviour that only holds in a secure context, so testing on a local file or a plain HTTP server will not reproduce what happens in production.

## The notes, and what they tell you about the technique

The Notes section is short and it is the most informative part of the README, because it tells you what the library cannot do.

False positives can occur in certain browser setups or in Chrome Guest mode. Firefox Container Tabs are not detected by this. The script must run over HTTPS. Brave and uBlock Origin block the CDN. And an error is thrown if the browser can't be identified.

The container tabs entry is a good one to think about, because it is a limitation of the technique rather than of the browser. Container Tabs are a Firefox feature that isolates browsing into separate containers, and they are not the same thing as private browsing. The library reports on private browsing, and container tabs are a different concept, so the non-detection is arguably correct behaviour rather than a gap.

The false positive warning in Chrome Guest mode is more awkward. Guest mode does restrict what a session can do, but the library's signal picks it up as private, and the README links the issue tracker for the details. The honest framing is that the library detects a class of browser states rather than a single well-defined one.

What all of this says about the technique is that it cannot be a direct API call, because no browser exposes one. The only available approach is to find a measurable difference in behaviour between a private and a normal window and test for it. That kind of detection is inherently heuristic, and heuristics have edges, which is why every note in this section is about a case where the heuristic does not hold.

The practical consequence for an adopter is that the result should be treated as a hint rather than a fact. It is good enough to decide whether to prompt a user differently, or to avoid storing something sensitive, but it is not a security control, because the user controls their own browser and can defeat any client-side detection.

The similar projects section points at two related services, an advanced browser fingerprinting and bot detection solution, and a positive email reputation API for fraud teams. The connection is the detection of automation and fraud, and it is worth noting that they are services rather than libraries.

## The package structure and the build

The package manifest is small and shows a deliberate publishing shape.

There are three entry points declared in the main fields. The main field is the CommonJS build at ./dist/detectIncognito.js, the module field is the ES module build at ./dist/detectIncognito.esm.js, and the types field is the declaration file at ./dist/detectIncognito.d.ts. So a bundler that understands modules gets the ES build, a plain require gets the CommonJS one, and TypeScript gets the types.

The build is two steps and the scripts make the order explicit. The build-for-cdn script runs webpack, and the build-module script runs tsc, and the build script runs the first and then the second.

That split matters. Webpack bundles the library into a single file for the CDN, and the CDN path in the README points at an es5 directory inside dist, so the bundled output is transpiled down to ES5 for maximum browser compatibility. The tsc step produces the module builds, which are not bundled and keep the modern output. So the two outputs have different targets, and building them separately is the way to get both right.

The dev dependencies are the toolchain for those two steps: webpack at about 5.75.0 with webpack-cli at about 5.0.1, ts-loader at about 9.4.2, terser-webpack-plugin at about 5.3.10 for minification, and typescript at about 4.6.4. The webpack config file at the root and the tsconfig.json alongside it are the two configuration files, and the .eslintrc.js is the lint setup.

The published files list is dist and src, so the source is shipped as well as the build. That is a deliberate choice for a library of this kind, because it lets a consumer who hits a bundler problem read the implementation rather than guess, and it makes the library patchable in a way a minified bundle is not.

The rest of the repository is the dist/ directory with the built output, an example.html that demonstrates the script in a page, the detectIncognito.svg logo, and the usual project files including a CONTRIBUTORS.md.

One detail in the metadata worth noting: the author field names Joe Rutkowski, and the README's licence line is Copyright 2026 Joe Rutkowski, distributed under the MIT licence. The package is a small, personal, well-maintained project rather than a corporate one, and the version numbers reflect that cadence, with two releases on the same day in August 2026.

## How to evaluate it, and what to check first

There are four things to consider.

First, and most importantly, what you are going to do with the answer. Because the detection is a heuristic on the client, and the client is under the user's control, it is not a security mechanism. A user who wants to defeat it can. If your use case is refusing to show a feature to a private window, this is fine, because the harm from being wrong is a user inconvenience. If your use case is refusing to display sensitive data, you need a server-side check, because anything the browser reveals can be suppressed.

Second, whether the browser you care about is in the table and within the version range. Chromium 83 to 150, Firefox 44 to 153, Safari up to 26.5, and MSIE 11 with a Promise polyfill. The MSIE entry is worth a second look, because Internet Explorer 11 is a long-dead browser, and the fact that it is still tested and supported with a polyfill note says the maintainer values a broad compatibility guarantee over effort.

Third, the CDN and adblocker question. If you serve the script from jsDelivr, Brave users and uBlock Origin users will not get it, and your feature will fail silently for a slice of your audience. The README's own advice is to host the script yourself, and if you already build a bundle, that is the path to take. If you install from npm, this does not apply at all.

Fourth, the HTTPS requirement. The script must run over HTTPS, and running it locally or over HTTP might fail. That is a development-time constraint that will otherwise look like a bug in the library, so plan to test on a real secure origin rather than a local server.

What you get is a small, well-scoped library that does one thing across the browsers you tend to meet, with honest notes about every case where it does not work, and a maintained version range that tells you the technique is still current. For a progressive-enhancement feature, where you would show one interface in a normal window and another in a private one, that is a reasonable amount of code to depend on.

## Conclusion

Adopt detectIncognito if you are building a progressive enhancement that behaves differently in a private window, and you want one small library that covers Chrome, Safari, Firefox, Edge, Brave, Opera and Internet Explorer rather than a set of per-browser snippets. Do not treat the result as a security control, because the detection is a heuristic that runs on the client under the user's control and can be defeated deliberately, so any decision that depends on it should be treated as a hint. Before you commit, read the notes, because false positives in Chrome Guest mode, the absence of Firefox Container Tab detection, the HTTPS requirement and the adblocker blocking of the CDN are all things that will otherwise look like bugs. If you serve it from a CDN, host it yourself instead, since Brave and uBlock Origin block the jsDelivr path, and check the version table against the browsers you actually have in your audience.

## FAQ

### What is detectIncognito?

It is a TypeScript library that detects incognito mode and other private browsing modes across most modern browsers, published on npm as detectincognitojs under the MIT licence. It covers Chrome, Safari on macOS and iOS, Firefox, Edge, Internet Explorer, Brave and Opera, and returns the detected browser name along with the private flag.

### How do I install and use detectIncognito?

Run npm i detectincognitojs, then import the named detectIncognito function and await the promise it returns, which resolves to an object with a browserName and an isPrivate boolean. A CDN build is also available as an es5 bundle on jsDelivr, though the README notes that adblockers including uBlock Origin and Brave may block it.

### Which browsers and versions does detectIncognito support?

The supported table lists Safari on all platforms up to 26.5, Chromium on all platforms from 83 to 150, Firefox on all platforms from 44 to 153, and MSIE on Windows at version 11, which requires a Promise polyfill.

### What are the known limitations of detectIncognito?

False positives can occur in certain browser setups and in Chrome Guest mode, Firefox Container Tabs are not detected, the script must run over HTTPS because running locally or over HTTP might fail, and an error is thrown if the browser cannot be identified.

### How is the detectIncognito package built?

Two separate steps. The build-for-cdn script runs webpack to produce the bundled CDN file, transpiled down to ES5, and the build-module script runs tsc to produce the CommonJS and ES module builds along with the type declarations. The published files are dist and src, so the TypeScript source ships alongside the built output.

## Sources

- [Issues](https://github.com/Joe12387/detectIncognito/issues)
- [Joe12387/detectIncognito on GitHub](https://github.com/Joe12387/detectIncognito)
- [License: MIT](https://github.com/Joe12387/detectIncognito/blob/main/LICENSE)
- [README](https://github.com/Joe12387/detectIncognito/blob/main/README.md)
- [Releases](https://github.com/Joe12387/detectIncognito/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/joe12387-detectincognito
