# ThumbmarkJS computes a browser fingerprint with no backend and no cookie

> ThumbmarkJS is a MIT licensed TypeScript library that derives an identifying hash from browser characteristics, with a component for audio, canvas, fonts, hardware, locales, maths, permissions, plugins, screen, speech, WebGL and WebRTC. The open source library reports around eighty percent uniqueness, and the paid API adds server side signals for the rest.

**thumbmarkjs/thumbmarkjs** — A free, open-source javascript fingerprinting library

- Repository: https://github.com/thumbmarkjs/thumbmarkjs
- Website: https://www.thumbmarkjs.com/
- Stars: 1,177 · Forks: 100
- Language: TypeScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/thumbmarkjs-thumbmarkjs

## Fingerprinting with no server in the loop

ThumbmarkJS is a free, open-source JavaScript browser fingerprinting library. Use it to identify returning visitors, detect fraud and bots, and more, all client-side, with no backend required.

The no backend part is the interesting design decision. Most visitor identification schemes need a server, because a cookie has to be set, stored and matched, and because the interesting signals are on the server side. This library computes everything in the browser and hands you a hash, so the only thing you need on your side is a place to store the hash.

That has consequences in both directions. For the visitor, there is no cookie to consent to and no identifier stored on a server they did not ask about. For the site operator, you lose the ability to correct or expire an identifier centrally, and you inherit the risk of the number in the next section.

The implementation is TypeScript, the licence is MIT and the project says it is commercially usable. The last push was on 2026-09-25, and the recent releases are v1.12.0 from 2026-09-25, v1.11.0 from 2026-08-25 and v1.10.1 from 2026-07-17, so the release cadence is roughly monthly.

The README also states that the library is used on 60,000 or more websites, generating over a billion fingerprints every month. That is the project making its own claim, and it is a reason to read the rest of the documentation carefully rather than a reason to trust it.

## The components, which are the interesting part

The published package manifest lists the source of every component, and the list is the clearest description of what a fingerprint is made of here.

There is a component each for audio, canvas, fonts, hardware, locales, math, permissions, plugins, screen, speech, WebGL and WebRTC, plus a system component with a browser file inside it, and the factory and options files at the top of the source tree.

Each of those probes a different part of the browser, and the interesting ones are the ones where browsers differ in ways a user cannot change. The audio component reads the rendering of an audio graph, the canvas component reads how text and shapes are rasterised, the fonts component asks what is installed, the hardware component asks about the graphics and audio devices, and the WebGL component reads the renderer and the driver strings. The permissions component asks what the browser will let you ask about, and the locales and the time zone and the screen and the platform fill out the rest.

The file list in the manifest also shows the utilities that make the result stable, and the names are a good description of the hard part. There is a cache, a commonPixels utility, an ephemeralIFrame, a getMostFrequent, a hash, an imageDataToDataURL, a raceAll, a sort, a stableStringify and a visitorId.

The stableStringify and the sort are the ones that matter for consistency, and the ephemeralIFrame and the raceAll are what let a component be measured without a permanent frame in the page. The design goal behind all of it is that the same browser produces the same hash today and tomorrow.

## It runs in a browser, and that is not a detail

The installation section is short, and it comes with a warning that shapes any plan for using it.

```bash
npm install @thumbmarkjs/thumbmarkjs
```

The warning is that ThumbmarkJS runs in the browser. It requires browser APIs to compute fingerprint components and cannot run server-side.

That is a hard architectural boundary. You cannot compute a fingerprint for a user who has not loaded your page, so the identifier only exists for a visitor who has executed JavaScript, and it cannot be used to identify a request that never reaches the client. If you are looking for a server side device identifier, this is the wrong library, and the readme is clear about it rather than leaving you to discover it at integration time.

There is also a CDN route, with transpiled bundles on jsDelivr in three formats, UMD, CommonJS and ESM, each with its own URL.

```bash
https://cdn.jsdelivr.net/npm/@thumbmarkjs/thumbmarkjs/dist/thumbmark.umd.js
```

And you can try it without installing anything, by pasting an import into the browser developer console and logging the result.

```javascript
import('https://cdn.jsdelivr.net/npm/@thumbmarkjs/thumbmarkjs/dist/thumbmark.umd.js')
  .then(() => {
    const tm = new ThumbmarkJS.Thumbmark();
    tm.get().then((res) => console.log(res));
  })
```

The live demo does the same thing with the individual components broken out, which is the more useful view when you are deciding what to include.

## Eighty percent uniqueness, and where it fails

The Performance section is the one to read before you design an identity system on top of this library, and the number is given plainly.

The client library achieves around eighty percent uniqueness across general browser populations. Mac and Safari users can show higher collision rates or higher noise depending on the audience.

That is an honest number and it is much lower than people assume when they read the word fingerprint. Roughly one in five browsers will share an identifier with another, and the Safari and Mac population is where the collisions concentrate, because the platform is more uniform and because some of the signals are noisier there.

The readme's response is to point at the paid API, which combines the client-side signals with server-side analysis including TLS handshake details, HTTP headers and connection-level data, and produces fingerprints with over ninety-nine percent uniqueness. It also adds smart signals: bot detection, VPN and TOR detection, datacenter traffic identification, and a threat level score per visitor. There is a free tier for that API.

So the honest framing is that the open source library is a component of an identification system rather than a complete one. Whether eighty percent is enough depends on what you are doing with it. Grouping sessions and rate limiting by a loose identifier is a different problem from uniquely identifying a person, and the same hash is adequate for one and useless for the other.

## Sampled logging, and the script it fetches

The Sampled logging section is unusually candid, and it is the part you should read before you enable anything.

By default, at most 0.01 percent of runs, and never more than once per browser session, send an anonymous sample to api.thumbmarkjs.com/log. This is how the library gets improved, because it shows which components turn out to be unstable across real browsers.

When a run is sampled, the library also fetches a small script from experimental.thumbmarkjs.com and evaluates it. That script computes fraud-prevention signals that are still under evaluation, and those signals are included in the sample but are not part of the fingerprint hash and are never returned to the caller. The stated reason for keeping them out of the bundle is that they can be iterated without a release.

The script is published in readable, non-minified form alongside the minified build the library actually loads, so you can read exactly what runs. Nothing is fetched or evaluated on the other 99.99 percent of runs, and the whole thing is disabled with a single option.

```javascript
const tm = new ThumbmarkJS.Thumbmark({ logging: false })
```

One more detail for people with a strict policy. If your content security policy omits unsafe-eval, or does not allow experimental.thumbmarkjs.com, the fetch or evaluation fails silently and fingerprinting is unaffected.

## The collect beacon and the paid API

The collect beacon is the second network path, and it only exists for paying users.

If you use a pro api_key, or proxy requests via simple_request, the API can occasionally instruct the client to send one small extra request, direct from the browser to collect.thumbmarkjs.com, or to your collect_endpoint.

```json
{
  "ua": "<navigator.userAgent>",
  "thumbmark": "<the resolved thumbmark hash>",
  "ja4": "<the JA4 TLS fingerprint the API observed>",
  "ja4_source": "<the header the API read it from>"
}
```

So the payload is the user agent, the hash, and the JA4 TLS fingerprint the API observed, along with the header it was read from. The receiving server also fingerprints the TLS handshake itself, which is the point: the client side signals are what the browser will give you, and the server side signals are what only the connection can tell you.

The collect_beacon option in the configuration table says when it fires, and the condition is worth understanding. It sends the User-Agent and thumbmark hash to collect.thumbmarkjs.com when the pro API asks the client to, and it never fires on an anonymous install, one that sets neither api_key nor simple_request, since no API call is made at all in that case.

The configuration table also lists the other knobs, and a few of them are about cost rather than about privacy. The exclude and include options set which components go into the hash, and excluding a top-level component also improves performance. permissions_to_check limits which browser permissions are checked, because the permissions are the slowest component to resolve. And performance, when true, includes per-component resolution time in milliseconds, which is the setting you want if you are debugging why the fingerprint takes two seconds.

## The repository, and the tooling around the components

The top level listing shows a project with serious testing, and a few entries are worth explaining.

There is a perf/ directory, which is the performance measurement side of a library whose whole job is to be fast enough to run on every page view. There is an e2e/ directory alongside a playwright.config.ts, so the library is tested in a real browser rather than only in a simulated DOM, which for a library that reads canvas and audio output is the only test that means anything. And there is a jest.config.js with a jest.setup.js for the unit level.

The documentation side is a docs/ directory, plus two files at the top level that are unusual and useful. technical_details.md explains how the components work, and testing_manifest.md is a manifest of the test coverage, which is a transparency gesture you do not often see.

The build is Rollup, with a rollup.config.mjs, and the Makefile is a thin wrapper around it that reads the version out of package.json with jq, builds, removes the types directory from the output and copies the UMD bundle to two locations outside the repository, the static site and the test page. There is a test.html at the top level, which is a manual test page for the built bundle.

The package metadata shows a modern dual build with an exports map that points types, import and require at three different files, and a files array that ships the component sources as well as the dist directory, which is how a consumer can import a single component rather than the whole library.

## Conclusion

Adopt the open source ThumbmarkJS library when you need a returning visitor identifier you can compute entirely in the browser, for example to rate limit without a cookie or to correlate sessions across a first party domain. Do not expect it to give you a unique identifier for every visitor, because the project reports around eighty percent uniqueness and higher collisions on Mac and Safari. Verify first which components you exclude and include, that your content security policy does not break the optional sampled logging path, and that you have read what the sampled logging actually sends, since a small fraction of runs phone home unless you turn it off.

## FAQ

### What is ThumbmarkJS?

It is a free, open-source JavaScript browser fingerprinting library, MIT licensed, for identifying returning visitors, detecting fraud and bots, all client-side with no backend required. The implementation is TypeScript and the package is published as @thumbmarkjs/thumbmarkjs.

### Can ThumbmarkJS run on the server?

No. The README warns that ThumbmarkJS runs in the browser, that it requires browser APIs to compute fingerprint components, and that it cannot run server-side. The identifier therefore only exists for a visitor who has executed JavaScript.

### How unique is the ThumbmarkJS fingerprint?

The client library achieves around eighty percent uniqueness across general browser populations, and Mac and Safari users can show higher collision rates or higher noise. The paid API adds server side signals such as TLS handshake details and HTTP headers, and claims over ninety-nine percent uniqueness.

### Does ThumbmarkJS send anything to a server?

By default at most 0.01 percent of runs, and never more than once per browser session, send an anonymous sample to api.thumbmarkjs.com/log, and a sampled run also fetches a script from experimental.thumbmarkjs.com. The collect beacon only applies when a pro api_key is in use, and it sends the user agent and hash to collect.thumbmarkjs.com.

### How do I turn the sampling off?

Construct the Thumbmark instance with logging set to false, which disables both the sample and the fetch of the experimental script. If your content security policy omits unsafe-eval or does not allow experimental.thumbmarkjs.com, the fetch or evaluation fails silently and fingerprinting is unaffected either way.

### How do I speed up the fingerprint calculation?

Use the exclude option to drop components you do not need, since excluding a top-level component also improves performance, and use permissions_to_check to limit the permissions probed, because they are the slowest component to resolve. The performance option adds per-component resolution times when you need to see where the time goes.

## Sources

- [License: MIT](https://github.com/thumbmarkjs/thumbmarkjs/blob/main/LICENSE)
- [Project website](https://www.thumbmarkjs.com/)
- [README](https://github.com/thumbmarkjs/thumbmarkjs/blob/main/README.md)
- [Releases](https://github.com/thumbmarkjs/thumbmarkjs/releases)
- [thumbmarkjs/thumbmarkjs on GitHub](https://github.com/thumbmarkjs/thumbmarkjs)

---

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