Library / SDK
oxidezap/baileyrs avatar
oxidezap/baileyrs

baileyrs: a Baileys-compatible WhatsApp library with a Rust core in WebAssembly

A Rust-powered WhatsApp Web library for JavaScript, with a Baileys-compatible API

40 stars16 forksTypeScriptMIT

At a glance

What is it?
A JavaScript wrapper that keeps the Baileys API surface while moving the Signal protocol, Noise handshake, media encryption and protobuf codec into Rust compiled to WebAssembly, with compatibility held in place by CI, contract audits and a differential fuzz suite.
Who is it for?
Adopt baileyrs if you already run Baileys and want the protocol layer off JavaScript without rewriting your imports, and if your reconnect handling can absorb terminal failures that Rust no longer retries for you. Hold off if you need a version of the declaration surface that passes a strict audit, or if your protobuf code depends on the bridge spelling rather than the Baileys one.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • oxidezap/baileyrs40 ★ · +15
Star history
Repository2026-08-272026-10-21
oxidezap/baileyrs2540

15 Stars in the last 55 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

A thin JavaScript wrapper, with the protocol work in Rust and WASM

baileyrs is a thin JavaScript wrapper, and the thinness is the point. The protocol work happens in Rust compiled to WebAssembly: the Signal protocol, the Noise handshake, media encryption, binary-node serialization and deserialization, and the protobuf codec all run through the @oxidezap/whatsapp-rust-bridge package. What JavaScript callers see is an API that deliberately mirrors Baileys, so integrations written against WhiskeySockets/Baileys can move across with minimal changes, and ATTRIBUTION.md carries the details of that relationship. The npm package is @oxidezap/baileyrs at version 0.3.9, MIT licensed, and the reference documentation lives on deepwiki rather than in the repository, covering sending messages and media, groups, privacy, newsletters, custom stores, socket configuration, error handling and memory monitoring. Inside the repository the source sits in src/, with runnable examples in Example/, compatibility tooling under scripts/, and agent-facing notes in AGENTS.md and CLAUDE.md beside a CHANGELOG.md. Linting and formatting have their own configuration files (.oxlintrc.json and .oxfmtrc.json), and an .npmignore keeps publish contents separate from what git tracks.

The compatibility claim is checked in CI, not asserted

CI packs the library locally and then compiles and runs consumer code against that tarball under the upstream package name, so the alias path is exercised rather than described. Published deep imports are run as well, and host initialization is verified in workerd with nodejs_compat disabled, which is the configuration a real Workers deployment has. Alongside those run the proto-runtime, wire-fidelity and lifecycle contract audits. The strict declaration audit is available as `npm run compat:audit:strict`, and the documentation is explicit that it currently reports known declaration gaps and is not a green gate yet. Layer ownership is checked separately through `npm run compat:layers`, which only runs when sibling core and bridge checkouts are available on the machine. Behavioural suites and a differential fuzz suite generate inputs from the proto schema and compare the two libraries directly, and whatever the fuzzers find is recorded with a reason and a review date, with open items listed in src/__fuzz__/harness/divergence.ts.

Aliasing the package is one command, with two exceptions

Migration is one command for most projects:

sh
npm install @whiskeysockets/baileys@npm:@oxidezap/baileyrs

That writes an alias into package.json, in the form of a dependency entry reading `npm:@oxidezap/baileyrs` with the version recorded at install time, and every `import { makeWASocket } from '@whiskeysockets/baileys'` in the codebase then resolves to baileyrs without a single source change. Installing the package directly always pulls two runtime dependencies: @oxidezap/whatsapp-rust-bridge, which is the library doing the protocol work rather than an add-on and therefore stays a hard dependency, and `long` alongside it. Two things still need attention. Carrying an existing pairing across takes a one-line import swap instead of the alias, which the migration section covers separately. And a connection.update handler written against a version before 0.1 has to change how it reads a `close` event: a close now always means the socket is finished and you have to recreate it. Code written against upstream Baileys already behaves that way, so this only affects projects that were already written for baileyrs itself and carried old handler logic forward.

Protobuf field spelling is the quiet way a port breaks

The subtle hazard in a protobuf port is not the wire format but the spelling. The bridge and upstream Baileys generate their schemas from different sources, so a field can keep its number and its wire type and still be spelled differently: deviceID against deviceId, deviceAgentID against deviceAgentId, faviconMMSMetadata against faviconMmsMetadata, and oldestMessageTimestamp against oldestMessageTimestampInWindow. Left alone that fails quietly, because a write in the Baileys spelling is dropped by the codec and a read never sets the public property, so the caller sees a missing field rather than an error. The proto facade therefore always uses the Baileys spelling, and both the facade and the send path translate in both directions, including for fields nested at any depth. An own public property wins over the bridge name, even when it is explicitly null or undefined, and inputs are never mutated. The bridge keeps its own names underneath.

Transient drops retry in Rust; terminal ones are still yours

Compared area by area, the differences concentrate in the transport. Signal protocol handling moves from JS libsignal to Rust/WASM, media encryption and decryption from Node.js crypto to Rust AES-256-CBC with HMAC, and key management from a JS auth state to a Rust PersistenceManager. Media upload and download is the largest change: where Baileys used fetch and temporary files, the Rust side adds CDN failover, auth refresh and resumable upload. Reconnect is the documented compromise. The manual `startSock()` loop is no longer needed for transient drops, which are retried inside Rust with fibonacci backoff, but terminal failures remain the caller's responsibility. That split changes who observes a dropped connection, so existing retry and alerting logic is the part of a migration most likely to need revisiting.

The /host entrypoint needs the WASM engine initialized first

Node.js 22.3 or newer is required for the Node API. Workers, Deno and browser-like hosts get a separate entrypoint, and there the bridge WASM has to be initialized before a socket is created:

ts
import makeWASocket, { createAuthenticationState, useMemoryStore } from '@oxidezap/baileyrs/host'
import wasm from '@oxidezap/baileyrs/wasm'
import { initSync } from '@oxidezap/whatsapp-rust-bridge/host'

initSync({ module: wasm })
const store = useMemoryStore()
const auth = await createAuthenticationState(store)
const socket = makeWASocket({ auth })

That path takes a caller-owned JsStoreCallbacks auth store and uses neither filesystem auth nor Node streams. Order matters beyond the socket: creating one initializes the bridge crypto engine during startup, so calling the standalone AES-backed media retry helpers without a socket means running `initWasmEngine()` yourself first. On a custom host runtime, `makeMediaCryptoRuntime(runtime)` returns a capability to pass as the final argument to `/host`'s `getMediaKeys`, `encryptMediaRetryRequest` and `decryptMediaRetryData`, keeping HKDF and AES-GCM on the bridge you selected instead of the default host singleton.

Rehydrating a device, and why you must not reset the record

Restarting outside Node has its own trap, and the guidance about it is unusually specific. Reuse the same byte-preserving store and call `await createAuthenticationState(store)` again: it rehydrates the device written by the Rust engine, including its registration ID, which may exceed the 14-bit ID used to seed fresh Baileys-style credentials. The documented warning is not to reset or truncate an existing device record to work around a failed read, since the discarded record is the one holding the rehydratable device. Node consumers have none of this to consider and should keep using the root entrypoint and `useMultiFileAuthState` unchanged. On the host path the store is the only place a session lives, so losing it means losing the paired device rather than falling back to a file.

The published tarball is narrower than the repository

What ships is smaller than what sits in the tree. package.json publishes lib/**/* while excluding source maps, __tests__, the __fuzz__ directory, and the *.test.* and *.test-e2e.* files, so the fuzz harnesses never reach consumers. The export map offers the root entry plus ./logger, ./host, ./wasm, and two open patterns, ./lib/* and ./lib/*.js, each resolving to declaration files as well as JavaScript. The build is a chain rather than one compiler call: it checks the waproto facade, cleans, compiles with tsconfig.build.json, runs tsc-esm-fix to rewrite extensions, then copies the facade into the build output. `npm run benchmark` points at Example/benchmark.ts, alongside Example/example.ts. Versions move through release-please, which pairs a manifest file with a release-please-config.json, with v0.3.7, v0.3.8 and v0.3.9 all published in September 2026 and the last push dated September 29, 2026.

Editorial conclusion

Adopt baileyrs if you already run Baileys and want the protocol layer off JavaScript without rewriting your imports, and if your reconnect handling can absorb terminal failures that Rust no longer retries for you. Hold off if you need a version of the declaration surface that passes a strict audit, or if your protobuf code depends on the bridge spelling rather than the Baileys one. Before migrating a paired device, read the pairing note and keep the existing auth record rather than regenerating it.

Frequently asked questions

How do I switch an existing Baileys project to baileyrs?

Install the alias with `npm install @whiskeysockets/baileys@npm:@oxidezap/baileyrs`. Every import of @whiskeysockets/baileys then resolves to baileyrs with no source changes, except for carrying an existing pairing across, which takes a one-line import swap instead.

Which Node version does baileyrs require?

Node.js 22.3 or newer for the Node API. Workers, Deno and browser-like hosts use the runtime-neutral /host entrypoint instead, with the bridge WASM initialized before the socket is created.

Why do protobuf property names differ between baileyrs and the bridge?

The two generate schemas from different sources, so a field can keep its number and wire type while being spelled differently, for example deviceID against deviceId. The proto facade always uses the Baileys spelling, because otherwise a write is dropped by the codec and a read never sets the property.

What should I know about the auth store when a baileyrs socket restarts?

Reuse the same byte-preserving store and call `await createAuthenticationState(store)` again, which rehydrates the device and its registration ID, one that may exceed the 14-bit ID used to seed fresh Baileys-style credentials. Do not reset or truncate an existing device record to work around a failed read.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/oxidezap-baileyrs.svg)](https://hysenlabs.com/projects/oxidezap-baileyrs)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/oxidezap-baileyrs.svg)](https://hysenlabs.com/projects/oxidezap-baileyrs)

Was this useful?