# Broprint.js turns canvas and audio signals into a 53-bit browser identifier

> Broprint.js is a zero dependency TypeScript library that fingerprints a browser by rendering a canvas and an offline audio graph, concatenating the two and hashing them with cyrb53 into a stable numeric string. The README is blunt about its limits, calling the hash a fast non-cryptographic checksum and warning against using it for authentication, password storage or anything needing collision resistance.

**Rajesh-Royal/Broprint.js** — The world's easiest, smallest and powerful visitor identifier for browsers.

- Repository: https://github.com/Rajesh-Royal/Broprint.js
- Website: https://broprintjs.netlify.app/
- Stars: 607 · Forks: 71
- Language: TypeScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/rajesh-royal-broprint-js

## A fingerprint instead of a tracking pixel

Broprint.js describes itself as the world's easiest, smallest and powerful visitor identifier for browsers, and the mechanism is that it generates a stable per-browser identifier from canvas and audio fingerprints, hashed with a fast, zero-dependency 53-bit hash. The README states the properties in one line: no tracking pixels, no third-party services, no runtime dependencies.

That combination is the pitch. A cookie or local storage identifier can be cleared by the user and is confined to one browser profile, so a site that wants to recognise a returning visitor across sessions has to fall back on something harder to remove. The browser itself is that something, and the signals used here are the two that are known to vary with the hardware and software stack rather than with the page.

There is a live demo hosted at broprintjs.netlify.app and a CodeSandbox you can open in a browser, so you can see the value it returns before writing any integration. The package is published on npm as @rajesh896/broprint.js, and the repository is MIT licensed with a SECURITY.md and a CONTRIBUTING.md.

The project is TypeScript, built with tsup, tested with vitest, and documented with typedoc. The default branch is develop, the last push was on 2026-04-25, and the published versions are v3.0.0 and v2.2.1, both from that day, with v3.0.0 carrying a MIGRATION.md because it broke things.

## The README tells you not to use it for security

The most useful thing in this README is a warning, and it is placed above the installation instructions rather than buried in a security section.

The hash, cyrb53, is a fast, non-cryptographic checksum. The README says to use it for visitor identification, analytics and fraud-signal heuristics, and not for authentication, password storage, or anything that requires collision resistance.

That is not a legal caveat, it is a description of the algorithm. A non-cryptographic checksum is fast and short, which is exactly what you want when you are hashing a canvas data URL on every page load, and exactly what you do not want when an attacker gets to choose the input. A 53-bit space is also small enough that a determined search can find collisions, and the returned value is a decimal string, which is a formatting convenience rather than a security property.

The honest way to use a fingerprint like this is as a signal, not as an identity. If your fraud heuristics look for a device identifier that has changed three times in a day, a 53-bit hash is fine for that. If your authentication depends on the identifier being unguessable, it is the wrong primitive, and the README says so before you write the code.

The privacy dimension is worth raising at the same time, because the same property that makes a fingerprint useful for recognition makes it uncomfortable for users. A value derived from the graphics stack survives clearing cookies and site data, which is a reasonable thing to want and a reasonable thing to tell people about. The library does not make that decision for you, so whatever consent and disclosure rules you operate under apply to this identifier exactly as they would to any other.

## The API is one function with three switches

The main entry point is getCurrentBrowserFingerPrint, which takes an optional options object and returns a promise of a string.

The options are three. useAudio includes the audio fingerprint signal and useCanvas includes the canvas fingerprint signal, both defaulting to true, and seed is a custom seed for the cyrb53 hash, defaulting to zero, which the README suggests using to namespace fingerprints per application so that two sites on the same domain do not correlate their visitors.

The return value is a promise of a stable numeric string, a 53-bit hash rendered in base 10. In the quick start the result looks like a sixteen digit number.

The failure behaviour is specified rather than left to chance, and that is worth reading. The function throws if both useAudio and useCanvas are false, so you cannot ask for a fingerprint with no signals. It throws if the only enabled signal fails, for example with useCanvas false on a browser that blocks OfflineAudioContext. And it throws if audio fails and canvas also fails.

There is one graceful path, and it is the common one in practice. If audio is enabled but fails, which the README attributes to privacy mode blocking OfflineAudioContext, and canvas is also enabled, the function automatically falls back to a canvas-only fingerprint. So on a browser that blocks the audio context you still get an identifier, and the identifier changes shape, which is worth knowing if you compare values over time.

You can also turn signals off yourself:

```js
await getCurrentBrowserFingerPrint({ useAudio: false });
```

and namespace the result with the seed option.

## How the two signals are produced

The how it works section draws the pipeline as a diagram, and it is a short one.

The audio side renders an OfflineAudioContext graph of 500 frames through a DynamicsCompressor, and the result is a float sum returned as a string. The point of the audio graph is that the floating point arithmetic in the compressor depends on the audio stack and the hardware, so two machines with the same browser can produce slightly different sums.

The canvas side draws layered text and shapes and reads back the data URI, which varies by the GPU and the font stack. The two strings are then concatenated and passed to cyrb53, which produces the 53-bit numeric hash that is coerced to a string.

The hash is described in the diagram as custom, with no dependencies, about ten lines. It is also described in the API section as a fast, non-cryptographic 53-bit hash, and the building blocks example shows it being used directly:

```js
const hash = cyrb53(canvas + audio); // → number in [0, 2^53)
```

The consequence of the design is that the identifier is stable for a given browser and machine combination, and changes when the hardware or the software stack changes. A browser update that changes the font rendering can change the canvas data URL and therefore the identifier. That is the expected behaviour of any fingerprint, and it is the reason a fingerprint is a signal rather than an account.

## The building blocks are exported, so you can build your own pipeline

The library does not only offer the combined function. The individual signal generators and the hash are exported too, and the README frames this as composing your own pipeline.

The named exports are getCanvasFingerprint, which renders the canvas and returns its toDataURL, and isCanvasSupported, which feature-detects HTML5 canvas with a 2D context. There is getAudioFingerprint, which renders the offline audio context graph and returns the float sum as a string, and cyrb53, the hash itself, plus the BroprintOptions TypeScript type for the options object.

The two generators have different signatures, and that difference matters if you build on them. The canvas one is synchronous and returns the data URL string. The audio one is asynchronous and returns a promise of a numeric string. So a hand-rolled pipeline is a mix of sync and async calls, and the hash takes the concatenation of the two:

```js
const canvas = getCanvasFingerprint(); // sync, returns canvas dataURL
const audio = await getAudioFingerprint(); // async, returns numeric string
```

Using the pieces directly lets you add a signal of your own, weight the two differently, or apply your own hashing, and is also how you would avoid the audio context entirely on browsers where it is a problem. The isCanvasSupported check is there for that reason, letting you branch before you call rather than catching a failure afterwards.

## Three builds, a hard size budget and a global script tag

The packaging is designed around a hard constraint, and the constraint is visible in the project configuration.

The size-limit configuration sets a limit of 2 KB for each of the three built artefacts, the ESM bundle, the CommonJS bundle and the global build. There is a size script and a size:why script that explains what is in the bundle. For a library that runs on every page load, a size budget enforced in the build is the mechanism that keeps the claim in the README honest.

The bundles themselves are produced by tsup, with the formats esm, cjs and iife, the global name Broprint, minification, and a clean output directory. The type declarations are emitted separately by tsc with emitDeclarationOnly, and the package exports map offers the root import with the import, require and default conditions, plus a separate global subpath export.

For use without a bundler there is a CDN route documented in the README, an ESM script tag that imports the module build from jsdelivr, and a classic script tag that loads the global build and uses the Broprint global directly. The classic path is the one to reach for when the page is not under your control and cannot be turned into a module.

The install section covers the package managers, with npm, yarn and pnpm all shown, and the package declares Node 18 or newer for the tooling, though the library itself is browser code and the requirement exists for the build.

## What the project looks like from the outside

The repository contents describe a well-organised project, and a few entries tell you what the maintainers care about.

There is a src directory for the TypeScript source and a lib directory for the built output, a docs directory, and an example directory that is a small site with Tailwind configuration, a PostCSS config and its own package file, so you can see the library used in a realistic page. There is a typedoc.json for the API documentation, a netlify.toml for the demo site, a .husky directory with lint-staged configuration in the package file, and an eslint config in flat config format with a prettier setup alongside it.

There is also a claude.md at the top level next to the changelog, a SECURITY.md, a CONTRIBUTING.md, a MIGRATION.md for the v3 upgrade and a codecov.yml. The presence of claude.md and a .github directory means the project expects contributions from coding assistants, and the lint-staged configuration runs eslint and prettier on staged TypeScript files before a commit.

On maintenance, the record is thin. The last push was on 2026-04-25, the same day as the v3.0.0 release and the v2.2.1 release, so the tree is in a released state. The repository is not archived and the licence is MIT.

The 53-bit identifier and the ten line hash are the reason to use this library, and they are also its limits. The library gives you a cheap, stable, dependency-free signal; everything that depends on the signal being unguessable has to be built elsewhere.

## Conclusion

Broprint.js fits a team that wants a stable per-browser identifier for analytics or fraud heuristics without a third-party script or a tracking pixel, and it does not fit a security system, because the README states plainly that the hash is a fast non-cryptographic checksum and not a security primitive. Before you adopt it, decide how you will handle the privacy side, since a fingerprint that survives cookie clearing is exactly the property that makes it useful for identification and also the property that makes consent handling your responsibility, and read MIGRATION.md if you are coming from version 2, because v3.0.0 carries breaking changes. The last push was on 2026-04-25, which shipped v3.0.0 and v2.2.1 on the same day.

## FAQ

### What does Broprint.js use to identify a browser?

It renders a canvas with layered text and shapes and reads back the data URI, renders an OfflineAudioContext graph of 500 frames through a DynamicsCompressor and takes a float sum, concatenates the two and hashes them with cyrb53 into a 53-bit number rendered as a base 10 string.

### Is Broprint.js safe to use for authentication?

No. The README states that it is not a security primitive, that the cyrb53 hash is a fast, non-cryptographic checksum, and that it should be used for visitor identification, analytics and fraud-signal heuristics rather than authentication, password storage or anything requiring collision resistance.

### How do I install Broprint.js and use it without a bundler?

Install it with npm i @rajesh896/broprint.js, or with yarn or pnpm. Without a bundler you can load the global build from a script tag on jsdelivr and use the Broprint global, or use an ESM module script tag that imports getCurrentBrowserFingerPrint from the module build.

### What happens if the audio fingerprint fails in Broprint.js?

If audio is enabled but fails, for example because privacy mode blocks OfflineAudioContext, and canvas is also enabled, the function automatically falls back to a canvas-only fingerprint. It throws if the only enabled signal fails, if both useAudio and useCanvas are false, or if audio and canvas both fail.

## Sources

- [License: MIT](https://github.com/Rajesh-Royal/Broprint.js/blob/develop/LICENSE)
- [Project website](https://broprintjs.netlify.app/)
- [Rajesh-Royal/Broprint.js on GitHub](https://github.com/Rajesh-Royal/Broprint.js)
- [README](https://github.com/Rajesh-Royal/Broprint.js/blob/develop/README.md)
- [Releases](https://github.com/Rajesh-Royal/Broprint.js/releases)

---

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