npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, πŸ‘‹, I’m Ryan HefnerΒ  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you πŸ™

Β© 2026 – Pkg Stats / Ryan Hefner

@vanzxy/baileys

v2.0.4

Published

VanzxyπŸƒ Baileys: an enhanced, ESM-only WhatsApp Web (multi-device) library for Node.js, crafted and maintained by VanzxyπŸƒ (Revan). Built on @itsliaaa/baileys and @whiskeysockets/baileys.

Readme



πŸ‘‘ Maintainer

Vanzxy


πŸƒ Why @vanzxy/baileys?

This fork focuses on:

| | | |---|---| | πŸš€ | Better developer experience | | 🎯 | Extended native flow support | | πŸ“’ | Advanced status & broadcast features | | 🧩 | More interactive message types | | ⚑ | Cleaner implementation for bot developers | | πŸ”§ | Extra utilities not available in standard Baileys forks | | πŸ—„οΈ | Multiple auth & store backends out of the box (file, SQLite, MongoDB, MySQL, PostgreSQL, Redis) | | πŸ“ž | Experimental voice-call (VoIP) support |


πŸ†š Comparison

How @vanzxy/baileys stacks up against its own lineage and the wider Baileys fork ecosystem:

| Capability | @vanzxy/baileys | @itsliaaa/baileys | WhiskeySockets/Baileys | |---|:---:|:---:|:---:| | Native Flow buttons (V1/V2/V3) | βœ… | βœ… | ⚠️ basic only | | Carousel messages | βœ… | ❌ | ❌ | | Rich response cards (AIRich) | βœ… | ❌ | ❌ | | Commerce flow (catalog/order/payment) | βœ… | ⚠️ partial | ❌ | | Voice calling (VoIP) | βœ… experimental | ❌ | ❌ | | Multi-backend store (SQL/Mongo/Redis) | βœ… | ⚠️ file/SQLite only | ⚠️ in-memory only | | Full .d.ts TypeScript definitions | βœ… | ⚠️ partial | ⚠️ partial | | User sync queries (WAUSync) | βœ… | βœ… | βœ… | | Username management (check/set/pin/recommend) | βœ… | ❌ | ❌ | | Bot framework (middleware, session, stats) | βœ… | ❌ | ❌ |

Tabel ini menggambarkan fitur di level package, bukan benchmark performa. PR untuk memperbarui/mengoreksi tabel ini dipersilakan lewat Contributing.


🧰 Built With


πŸ”₯ Features

  • Native Flow
  • Buttons (V1 / V2 / V3)
  • Lists
  • Carousel Messages
  • Poll & Quiz Messages
  • Rich Response Messages (AIRich)
  • CTA / Reply / URL Buttons
  • Call Buttons
  • OTP Buttons
  • Authentication Buttons
  • Catalog Message
  • Order Details
  • Order Status
  • Review & Pay
  • Payment Status
  • Payment Method
  • Track Order
  • Reorder
  • Cancel Order
  • Group Status Support
  • Mention All
  • Lottie Sticker Support
  • Newsletter Support
  • ExternalAdReply Helper
  • View Once Support
  • Rich Formatting
  • Code Highlighting
  • Multi-file auth state
  • Single-file auth state
  • SQLite auth state
  • Cache-manager auth state
  • In-memory store
  • SQLite / MongoDB / MySQL / PostgreSQL / Redis store adapters
  • Voice calling (VoIP, WASM engine)
  • User sync queries (WAUSync)
  • Presence / status / device lookup
  • Event buffer for high-volume bots
  • Full TypeScript definitions (.d.ts)
  • Anti-delete detection
  • Message search helpers
  • Auto-reply engine
  • Scheduling helpers
  • Message retry manager

✨ Exclusive Vanzxy Enhancements

Audio Group Status Fix

Audio status uses a more compatible implementation to avoid unsupported-version errors on older WhatsApp clients.

AIRich β€” the rich-response message builder

A chainable builder (AIRich, also exported as AIVanzxy / LeafRich / VanzxyAI / VanzxyRich) with 40+ add*()/set*() methods covering headings, formatted text (hyperlinks/citations/LaTeX), code blocks, tables, image/video/product/post cards, task & progress cards, tip banners, and quick-reply suggestions β€” the kind of rich card layout you'd normally only see from an official AI/assistant-style bot. See Usage Examples below.

Native Flow Expansion

Buttons & Actions

  • cta_reminder
  • cta_cancel_reminder
  • otp_button
  • authentication_button
  • call_button
  • url_button
  • reply_button
  • voice_call
  • video_call_button

Commerce Flow

  • catalog_message
  • mpm
  • card_message
  • order_details
  • order_status
  • review_and_pay
  • payment_status
  • payment_method

Navigation & Misc

  • address_message
  • send_location
  • track_order
  • reorder
  • cancel_order
  • clear_chat
  • navigateToScreen
  • flow_action

System Notification Filter

Messages include:

message.isSystemNotification

Useful for filtering:

  • E2E notices
  • Meta service notices
  • Other system-generated messages

πŸ“¦ Installation

npm install @vanzxy/baileys

Directly from GitHub:

npm install github:vanzxysenpai/vanzxybaileys

Requires Node.js 20+ β€” install will refuse to run below that (checked by engine-requirements.js on preinstall).

Optional peer dependencies

Everything below is optional β€” the socket works without any of them. Install only what the features you use need; missing ones fail with a clear install hint instead of a silent crash.

| Package | Unlocks | |---|---| | sharp | Fast image resizing/processing (used by the message builders' Toolkit.resize) | | @napi-rs/image | Lighter native alternative to sharp for image ops | | jimp | Pure-JS image fallback when neither of the above is installed | | fluent-ffmpeg | Audio/video conversion for media messages | | audio-decode | Audio waveform/duration extraction (voice notes, VoIP capture) | | link-preview-js | Rich link previews for URLs in outgoing text messages | | better-sqlite3 | SQLite auth state, SQLite store adapter, and the Bot Framework's SQLiteStore/StatsManager | | node-webpmux | Packname/author EXIF metadata on stickers made via MediaManager.convertToSticker() β€” not needed for plain sticker conversion | | mongodb | MongoDB store adapter | | mysql2 | MySQL store adapter | | pg | PostgreSQL store adapter | | ioredis | Redis store adapter | | @roamhq/wrtc | Native WebRTC bindings for voice calling |


πŸš€ Quick Start

import { makeWASocket, useMultiFileAuthState } from '@vanzxy/baileys'

const { state, saveCreds } = await useMultiFileAuthState('auth_info')

const sock = makeWASocket({
    auth: state
})

// persist credentials whenever Baileys updates them
sock.ev.on('creds.update', saveCreds)

sock.ev.on('connection.update', (update) => {
    const { connection, qr } = update

    // `printQRInTerminal` is deprecated upstream β€” render the QR yourself,
    // e.g. with the `qrcode-terminal` package:
    //   import qrcode from 'qrcode-terminal'
    //   if (qr) qrcode.generate(qr, { small: true })
    if (qr) console.log('Scan this QR:', qr)

    if (connection === 'open') console.log('πŸƒ Connected!')
})

sock.ev.on('messages.upsert', ({ messages }) => {
    const msg = messages[0]
    if (!msg.message || msg.key.fromMe) return
    console.log('New message from', msg.key.remoteJid)
})

πŸ” Authentication

Four auth-state backends ship out of the box. All return the same { state, saveCreds } shape expected by makeWASocket({ auth }), so they're drop-in interchangeable.

| Function | Storage | Best for | |---|---|---| | useMultiFileAuthState(folder) | One JSON file per key, on disk | Default choice β€” simple, debuggable, works everywhere | | useSingleFileAuthState(fileName) | One JSON file, on disk | Small bots where a single file is easier to manage/back up | | useSqliteAuthState(opts) | SQLite (better-sqlite3) | Bots that already use SQLite, or want auth in one embedded DB file | | useCacheManagerAuthState(store, sessionKey) | Any cacheable-compatible store | Multi-session hosting panels, Redis-backed setups |

import { makeWASocket, useMultiFileAuthState } from '@vanzxy/baileys'

const { state, saveCreds } = await useMultiFileAuthState('auth_info')
const sock = makeWASocket({ auth: state })
sock.ev.on('creds.update', saveCreds)
// SQLite variant
import { makeWASocket, useSqliteAuthState } from '@vanzxy/baileys'

const { state, saveCreds } = await useSqliteAuthState({ database: './auth.db' })
const sock = makeWASocket({ auth: state })
sock.ev.on('creds.update', saveCreds)

useMultiFileAuthState also exports pruneStaleAuthFiles(folder, options) to clean up old sender-key files on a schedule β€” useful for long-running bots that accumulate thousands of stale key files.


πŸ—„οΈ Store Backends

makeInMemoryStore() from lib/Store gives you the classic in-memory chat/contact/message cache. For anything that needs to survive a restart, makePersistentStore() (from PersistentStore.js) wraps one of five backends behind the same interface:

| Backend | Function | |---|---| | SQLite | createSqliteStoreAdapter(opts) | | MongoDB | createMongoStoreAdapter(opts) | | MySQL | createMysqlStoreAdapter(opts) | | PostgreSQL | createPostgresStoreAdapter(opts) | | Redis | createRedisStoreAdapter(opts) |

import { makeWASocket, makeInMemoryStore } from '@vanzxy/baileys'

const store = makeInMemoryStore({})
store.readFromFile('./baileys_store.json')
setInterval(() => store.writeToFile('./baileys_store.json'), 10_000)

const sock = makeWASocket({ /* ...auth etc */ })
store.bind(sock.ev)

πŸ’‘ Usage Examples

Buttons & Native Flow

import { Button } from '@vanzxy/baileys'

await new Button(sock)
    .setTitle('Promo Spesial')
    .setBody('Diskon 20% cuma hari ini')
    .setFooter('@vanzxy/baileys')
    .addReply('Klaim Sekarang', 'claim_promo')
    .addUrl('Lihat Katalog', 'https://example.com/catalog')
    .addCall('Hubungi Kami', '628123456789')
    .send(jid)

Poll

import { Poll } from '@vanzxy/baileys'

await new Poll(sock)
    .setName('Mau makan apa hari ini?')
    .addOptions(['Nasi Goreng', 'Mie Ayam', 'Bakso'])
    .setSelectable(1)
    .send(jid)

Carousel

Each card must be built with Button(...).toCard() first β€” a carousel card is really just a button card with an image/video header.

import { Button, Carousel } from '@vanzxy/baileys'

const cardA = await new Button(sock)
    .setImage('https://example.com/a.jpg')
    .setTitle('Produk A')
    .addUrl('Lihat', 'https://example.com/a')
    .toCard()

const cardB = await new Button(sock)
    .setImage('https://example.com/b.jpg')
    .setTitle('Produk B')
    .addUrl('Lihat', 'https://example.com/b')
    .toCard()

await new Carousel(sock)
    .setBody('Pilih salah satu produk di bawah ini')
    .addCard([cardA, cardB])
    .send(jid)

AIRich β€” rich response cards

import { AIRich } from '@vanzxy/baileys'

await new AIRich(sock)
    .addHeading('Ringkasan Order')
    .addText('Pesananmu sedang diproses.')
    .addTable([
        ['Item', 'Qty'],
        ['Kopi Susu', '2']
    ])
    .addTip('Pesanan biasanya siap dalam 15 menit')
    .addSuggest('Lacak Order')
    .send(jid)

AIRich supports { id, insertAt } on every add*()/set*() call, so you can insert a block relative to one you added earlier instead of always appending to the end β€” handy for streaming/edit-in-place style responses combined with sendEdit(jid, id).

Other builders worth knowing about: ButtonV2 (simpler quick-reply-only buttons), ButtonV3 (loadFrom(msg) to edit an existing template message in place), and Toolkit (static helpers: Toolkit.resize(), Toolkit.fetchBuffer(), Toolkit.waitAllPromises(), Toolkit.extractIE() for parsing [label](url) links/citations/LaTeX out of plain text).

πŸ“ Builders Folder Map (lib/Builders/)

Every message builder lives in its own file under lib/Builders/, so you can trace exactly where a class comes from instead of digging through one giant MessageBuilder.js. index.js is just the barrel file that re-exports everything below (plus their aliases) for the top-level import { ... } from '@vanzxy/baileys' syntax.

| File | Exports | What it's for | |---|---|---| | shared.js | Toolkit, BaseBuilder, RowBuilder | The shared foundation every other builder is built on. BaseBuilder holds the common chainable send()/context-info plumbing, RowBuilder is the shared row/section helper, and Toolkit is the static-helper grab bag (resize, fetchBuffer, waitAllPromises, extractIE, media-duration/preview helpers). Nothing here is meant to be instantiated directly in bot code β€” it exists so Button, Poll, Carousel, etc. don't each reimplement the same plumbing. | | Button.js | Button, CardBuilder | The main interactive/native-flow button builder β€” headers (image/video/document), CTA helpers (addUrl, addCall, addReply, and the wider native-flow set), and .toCard() to turn a button message into a Carousel card. | | ButtonV2.js | ButtonV2 | A lighter-weight variant limited to simple quick-reply buttons β€” reach for this when you don't need the full native-flow surface of Button. | | ButtonV3.js | ButtonV3 | Built around loadFrom(msg) β€” loads an existing templateMessage (e.g. one you fetched or that was quoted) so you can edit it in place instead of building one from scratch. | | Carousel.js | Carousel | Chains Button(...).toCard() results into a swipeable card carousel. Capped at Carousel.MAX_CARDS (10) since WhatsApp silently truncates anything beyond that. | | Poll.js | Poll | Poll/vote message builder β€” question, options, single/multi-select, hidden-voter mode, correct-answer marking, expiry. | | AIRich.js | AIRich (+ aliases ORich, AIVanzxy, LeafRich, VanzxyAI, VanzxyRich, RichVanzxy) | The rich AI-assistant-style response builder described above β€” headings, formatted text, tables, media/product/post cards, task/progress cards, tip banners, quick-reply suggestions. | | A2UI.js | A2UI, sendA2UIWidget | Lower-level A2UI/Bloks widget builder. Builds the flat components tree (Column/Row β†’ children, Card/Button β†’ child, Modal β†’ trigger/content) that Bloks widgets expect and sends it through the same getBizBinaryNode() path as Button/ButtonV2, so the wire-level node always matches the button names actually sent. | | VanzxyBaileys.js | VanzxyBaileys | A unified builder hub β€” one object that wraps all the builders above behind short method names (.button(), .buttonV2(), .buttonV3(), .carousel(), .poll(), .airich(), .a2ui()), plus PascalCase aliases (.Button(), .Carousel(), .Poll(), .AIRich(), .A2UI()) for developers who prefer that naming style. Nothing new is implemented here β€” it's purely a single entry point over the individual classes. | | index.js | everything above, plus MESSAGE_BUILDER_VERSION | The barrel file β€” re-exports every builder and its aliases so import { Button, Poll, AIRich, VanzxyBaileys } from '@vanzxy/baileys' works without reaching into individual files. MESSAGE_BUILDER_VERSION tracks the builder API surface's own version, independent of the package version. |

Unified hub example β€” useful if you'd rather carry one object around than import each builder individually:

import { VanzxyBaileys } from '@vanzxy/baileys'

const vx = new VanzxyBaileys(sock)

await vx.button()
    .setTitle('Promo Spesial')
    .addReply('Klaim Sekarang', 'claim_promo')
    .send(jid)

await vx.poll()
    .setName('Mau makan apa hari ini?')
    .addOptions(['Nasi Goreng', 'Mie Ayam'])
    .send(jid)

πŸ“ž Voice & Video Calls

Experimental audio-call support (ported from ourin-baileys) via a WASM call stack + WebRTC relay. Requires the optional @roamhq/wrtc peer dependency.

import { VoipClient } from '@vanzxy/baileys'

const voip = new VoipClient({ resourcesPath: './voip-resources' })
await voip.connectWithSocket(sock)

const call = await voip.call('628123456789')

call.on('ringing', () => console.log('Ringing...'))
call.on('connected', () => console.log('Call connected'))
call.on('ended', (reason) => console.log('Call ended:', reason))

ActiveCall (returned by .call()) and CallState are also exported directly if you need finer-grained control over call state.


πŸ”Ž User Sync Queries

WAUSync (USyncQuery / USyncUser + protocols) lets you check things like WhatsApp registration, device lists, status, and username info for a JID before you message it β€” the same mechanism behind sock.onWhatsApp().

import { USyncQuery, USyncUser, USyncContactProtocol } from '@vanzxy/baileys'

const query = new USyncQuery()
    .withContext('interactive')
    .withMode('query')
    .withUser(new USyncUser().withPhone('628123456789'))

query.protocols.push(new USyncContactProtocol())

const result = await sock.executeUSyncQuery(query)

Available protocols: USyncContactProtocol, USyncDeviceProtocol, USyncStatusProtocol, USyncUsernameProtocol, USyncDisappearingModeProtocol, UsyncBotProfileProtocol, UsyncLIDProtocol.

New in 2.0.3 (ported from nexustech): USyncBusinessProtocol, USyncPictureProtocol, USyncTextStatusProtocol, USyncSidelistProtocol, USyncFeatureProtocol, each with a chainable helper on USyncQuery:

const query = new USyncQuery()
    .withContext('interactive')
    .withMode('query')
    .withUser(new USyncUser().withId('[email protected]'))
    .withBusinessProtocol()      // -> { verifiedName, verifiedLevel, profileTag, pnJid }
    .withPictureProtocol()       // -> { id, directPath, hash }
    .withTextStatusProtocol()    // -> { text, emoji, setAt, expiresAt }

const result = await sock.executeUSyncQuery(query)

πŸ‘€ Username Management

High-level wrappers around WhatsApp's username feature (the @username handle you can set instead of exposing your phone number), sitting on top of USyncUsernameProtocol. Query IDs are captured from live WA Web sessions β€” if a call starts throwing unexpected response structure, WA has rotated them and they need re-capturing.

// Check availability + get suggestions if taken
const check = await sock.checkUsername('vanzxy')
// { available: true, username: 'vanzxy' }
// or: { available: false, suggestions: [...], rejectionReasons: [...] }

// Claim a username
await sock.setUsername('vanzxy', { source: sock.USERNAME_SOURCE.USER_INPUT })

// Lock it behind a PIN so it can't be changed without one
await sock.setUsernamePin('123456')

// Read your own username / drop it
const mine = await sock.getMyUsername()
await sock.deleteUsername()

// Resolve a username to a JID (USync-based, like onWhatsApp() but by username)
const user = await sock.findUserByUsername('someone')
// { jid: '[email protected]', contact: false }

// Batch-resolve usernames for a list of known contacts
const usernames = await sock.fetchContactUsernames(jid1, jid2, jid3)

// Get WA's own suggestions (e.g. for onboarding flows)
const recs = await sock.getUsernameRecommendations()

Requires a Community/Contact-tier account in good standing β€” accounts under WA's usual restrictions for new/unverified numbers may see INVALID or empty suggestions regardless of the username's actual availability.


πŸ€– Bot Framework

An optional, higher-level layer on top of the raw socket: middleware routing, a !command dispatcher, a message queue that survives disconnects, exponential-backoff auto-reconnect, per-JID session storage, group activity stats, and media (sticker/voice-note) conversion helpers β€” so a new bot project doesn't have to hand-roll session/context management every time.

Needs the better-sqlite3 peer dependency (session + stats storage) and fluent-ffmpeg (sticker/voice-note conversion, already listed above) β€” both fail with an install hint rather than crashing if you use a Framework feature that needs them without installing them first.

import { Bot } from '@vanzxy/baileys'

const bot = new Bot({
    socketConfig: { printQRInTerminal: true },
    dbPath: './bot.db',   // sessions + stats, defaults to 'baileys_store.db'
    enableStats: true     // group message/sticker leaderboards + ghost detection
})

bot.command('!ping', async (ctx) => {
    await ctx.reply({ text: 'pong' })
})

bot.command('!sticker', async (ctx) => {
    if (!ctx.quoted?.imageMessage) return ctx.reply({ text: 'Reply to an image with !sticker' })
    // ctx.replySticker() handles the WebP conversion for you
    await ctx.replySticker(imageBuffer, { packname: 'Vanzxy Pack', author: 'you' })
})

bot.onText(async (ctx) => {
    // ctx.session() / ctx.setSession() / ctx.updateSession() / ctx.clearSession()
    // persist small per-chat state (e.g. multi-step flows) to SQLite automatically
    const state = ctx.session()
    if (state?.awaitingReply) {
        ctx.updateSession((s) => ({ ...s, awaitingReply: false }))
    }
})

await bot.start()

Context, SessionManager, StatsManager, MediaManager, and SQLiteStore are also exported individually if you only need one piece rather than the full Bot class.

These files ship as plain .js for now β€” hand-written .d.ts declarations for the Framework module haven't been added yet, unlike the rest of this fork's fully-typed surface.


🧩 Utility Modules

A sample of the utilities exported from lib/Utils beyond the message builders above:

| Module | What it does | |---|---| | anti-delete | Detect and recover messages the sender deleted for everyone | | auto-reply | Simple keyword/pattern-based auto-responder engine | | message-search | Search cached/stored messages, peeling off ephemeral/view-once wrappers first | | message-retry-manager | Handles WhatsApp's retry-receipt protocol for undecryptable messages | | scheduling | Schedule messages/actions for later delivery | | business | Business-profile & catalog helpers | | chat-control | Pin, mute, archive, and mark-read/unread helpers | | chat-history-helpers | Work with synced chat history payloads | | link-preview | Generate link preview metadata for outgoing messages | | stickerpack | Build and send sticker packs (including animated/Lottie) | | templates | Legacy WhatsApp Business template message helpers | | vcard | Build vCard (contact card) payloads | | status | Post and manage WhatsApp Status updates | | event-buffer | Buffers/coalesces high-volume socket events for heavier bots | | reconnect-backoff | computeReconnectDelay() / createReconnectBackoff() β€” full-jitter exponential backoff (2s base, 15 min cap) | | bad-mac-handler | Detect/count "Bad MAC" (out-of-sync Signal session), safe-wrap async fns, optional session-* / sender-key-* cleanup | | connection-watchdog | setupConnectionWatchdog() / opt-in makeWASocket({ connectionWatchdog: true }) liveness probe | | auto-reconnect | autoReconnect(factory, opts) β€” drop-in reconnect manager (never reconnects after loggedOut) | | auth-secure | Encrypted auth state at rest (AES-256-GCM), backup/restore, integrity checks, pairing/QR guards, secret redaction | | humanizer | sendHumanized() β€” typing presence + length-based delay + per-chat send queue | | mock-socket | createMockSocket() β€” offline stand-in for sock, to test bot logic without a WhatsApp account | | meta-ai | askMetaAI() β€” send a prompt to Meta AI and await the reply (experimental) | | meta-ai-msmsg | Experimental, unwired msmsg decrypt helpers (see the note in NOTICE.md / changelog) | | qr-terminal | printQRIfNecessaryListener(ev, logger) β€” opt-in terminal QR printer (lazy qrcode-terminal, returns an unsubscribe fn) | | disconnect-reason | mapDisconnectReason(), isFatalDisconnect(), shouldClearAuth(), shouldReconnect(), getDisconnectInfo() β€” stable names for disconnect codes | | phone-utils | validateE164(), jidToPhone(), phoneToJid() | | text-tools | parseMentions(), extractGroupInviteCode(), joinGroupViaLink() | | pairing-utils | normalizePairingCode(), formatPairingCode(), pairingErrorCode(), isRateLimitError(), isCustomPairingError() | | group-metadata-cache | isUsableGroupMetadata() β€” relay uses it to refetch cached groups that lack a valid addressingMode | | username | validateUsername(), isUsernamePin(), displayUsername() β€” client-side username validation | | jid-tools | getPhoneNumber(), resolveSenderPn(), getLidForPhone() β€” LID <-> phone number via the persistent LID mapping | | tag | tagAll(), hideTag(), buildTagContent() β€” mention every group member | | ffmpeg-path | setFfmpegPath(), resolveFfmpegPath(), requireFfmpegPath() β€” one resolver for the ffmpeg binary (FFMPEG_PATH, ffmpeg-static, system PATH) | | voice-note | sendVoiceNote(), buildVoiceNoteContent() β€” converts to mono 16 kHz Opus/OGG and sends as PTT | | text-format | splitText(), whatsappify(), sendLongText() β€” chunk long text / convert markdown to WhatsApp formatting | | lock-manager | makeLockManager() β€” deterministic keyed lock manager | | migrate-auth-state | migrateAuthState({ from, to }) β€” copy creds + keys between auth stores (needs keys.list) | | passkey | getPasskeyRequestState(node) β€” passkey companion-linking request parsing |

Every module above ships a matching .d.ts, so your editor will show full hover-docs regardless of which ones you import.

Stability & security quick start (2.0.3)

import makeWASocket, { autoReconnect, useEncryptedFileAuthState, sendHumanized } from '@vanzxy/baileys'

// creds + keys encrypted on disk (legacy plaintext folders are read and migrated on next save)
const { state, saveCreds } = await useEncryptedFileAuthState('./auth', { password: process.env.AUTH_PASSWORD })

const manager = autoReconnect(() => makeWASocket({ auth: state }), {
    onSocket: (sock) => {
        sock.ev.on('creds.update', saveCreds)
        sock.ev.on('messages.upsert', async ({ messages }) => {
            await sendHumanized(sock, messages[0].key.remoteJid, { text: 'pong' })
        })
    },
    onLoggedOut: () => console.log('session logged out β€” wipe ./auth and re-pair'),
})
await manager.start()

Test bot logic offline with createMockSocket():

import { createMockSocket } from '@vanzxy/baileys'

const mock = createMockSocket()
myBotSetup(mock.sock)                              // your real bot code, unchanged
await mock.receiveText('[email protected]', '!ping')
const out = await mock.waitForReply()

πŸ›  Recommended Environment

| Requirement | Version | |---|---| | Node.js | 20+ | | Module system | ESM | | WhatsApp | Latest Multi Device |


πŸ“˜ TypeScript Support

Every file in lib/ ships a matching .d.ts, including the message builders (MessageBuilder_d.ts) and all Types/* definitions (Auth, Chat, Contact, Events, GroupMetadata, Message, Product, Signal, USync, and more). No @types/ package needed β€” plain import { ... } from '@vanzxy/baileys' gets full autocomplete in TS or JS-with-checkJS projects.


πŸ“š Fork Lineage

@vanzxy/baileys
└── @itsliaaa/baileys
    └── WhiskeySockets/Baileys

Respect to all original maintainers and contributors.


❓ FAQ & Troubleshooting

Cek connection.update untuk field lastDisconnect.error. Kalau status code-nya 401 (loggedOut), sesi memang sudah invalid dan perlu scan ulang QR β€” jangan auto-reconnect di kondisi ini. Untuk status lain (428, 440, dsb.), reconnect dengan backoff biasanya cukup.

printQRInTerminal sudah deprecated di upstream Baileys β€” pastikan kamu render QR sendiri dari event qr (lihat contoh di Quick Start). Kalau QR muncul tapi gagal linking, biasanya karena versi WA Web (version di makeWASocket) sudah kedaluwarsa; fetch versi terbaru lewat fetchLatestBaileysVersion().

Umumnya terjadi kalau folder auth state korup atau sesi dipakai di lebih dari satu proses secara bersamaan. Pastikan hanya satu instance yang menulis ke folder auth state yang sama, dan pertimbangkan pruneStaleAuthFiles() untuk membersihkan sender-key lama secara berkala.

Kalau pakai makeInMemoryStore(), cache chat/message/contact akan terus tumbuh tanpa batas. Untuk bot yang jalan lama, pertimbangkan pindah ke salah satu backend makePersistentStore() (SQLite/Redis/dst) dan pakai event-buffer untuk meredam lonjakan event di trafik tinggi.

Fitur ini masih experimental dan butuh peer dependency @roamhq/wrtc β€” pastikan sudah terinstall dan platform kamu didukung native binding-nya. Cek event call.on('ended', reason => ...) untuk detail penyebab gagalnya.


🀝 Contributing

Kontribusi dipersilakan, terutama untuk perbaikan bug, dokumentasi, dan enhancement pada MessageBuilder / AIRich.

  1. Fork repo ini, buat branch dari main (feat/nama-fitur atau fix/nama-bug)
  2. Pastikan perubahan tetap ESM-only dan menyertakan/menyesuaikan .d.ts terkait
  3. Uji perubahan pada minimal satu jalur auth state + satu store backend sebelum PR
  4. Buka Pull Request dengan deskripsi singkat: apa yang berubah dan kenapa

Untuk laporan bug, sertakan versi Node.js, cara reproduksi, dan potongan log lastDisconnect.error bila relevan.


πŸ™ Credits

  • Nixel (with contributions from Ahmad tumbuh kembang) β€” base implementation of the MessageBuilder (AIRich / Button / Carousel / Toolkit), originally "NIXCODE β€” Advanced WhatsApp Interactive Message Builder"
  • vinikjkkj (zapo) β€” button/list addon-kind resolution logic
  • QueenAnya (@queenanya/baileys) β€” interactive/native-flow button send layer
  • JAPofc (@japofc/baileys) β€” auto-reconnect manager, encrypted auth state / security pack, humanized sender, offline mock socket, Meta AI helper
  • nexustechpro2 (@nexustechpro/baileys) β€” extra USync protocols (Business, Picture, TextStatus, Sidelist, Feature)
  • lpzeravk / systemzero β€” reconnect backoff, Bad MAC handler, connection watchdog
  • WhiskeySockets/Baileys & @itsliaaa/baileys β€” upstream base

Full details and license terms per component: see NOTICE.md.



⚠️ Disclaimer

This project is an independent fork. Use responsibly and follow WhatsApp Terms of Service.


πŸ“ v2.0.4 Patch Notes

  • Bug fix (bug 85, VanzxyBaileys.js): a2ui() returned new A2UI() with no client, which is actually correct on its own (A2UI's constructor genuinely doesn't take one β€” it's a pure spec builder). The real problem: A2UI.send(client, jid, opts) takes client as its first argument, while every sibling factory off this hub (button(), buttonV2(), buttonV3(), carousel(), poll()) pre-binds #client at construction and exposes send(jid, opts). Calling hub.a2ui().send(jid, opts) the same way you'd call any other builder off the hub silently passes jid as client and opts as jid β€” exactly the inconsistency this "unified builder hub" exists to prevent. Fixed by wrapping the returned instance's send() to pre-curry this.client, so it matches the hub's own calling convention; the standalone new A2UI() + sendA2UIWidget(client, jid, {...}) path (used without this hub) is untouched.
    • Read lib/Builders/ in full: index.js, Poll.js, blockTracking.js, Carousel.js, VanzxyBaileys.js, A2UI.js, ButtonV2.js, ButtonV3.js, and Button.js (962 lines) line by line β€” no other confirmed bugs; a couple of things were checked and ruled out as false alarms (CardBuilder.title()/.text() calling Button.setTitle()/.setBody(), which turned out to be correctly inherited from BaseBuilder). AIRich.js (4090 lines, by far the most heavily audited file in the fork across prior sessions β€” see the file's own extensive Vanz@Fix/Vanz@Add history) was sampled rather than read exhaustively: the constructor, the build()/send()/buildEdit()/sendEdit() integration tail, and a cross-section of add*() primitive methods (addFile, addComment, addActionList, addSourceFull, etc.) all checked out, plus an automated sweep for empty catches/console.*/TODOs came back clean β€” but given the file's size, that's narrower coverage than the full line-by-line pass the rest of this round got, and is flagged as such rather than claimed complete.
  • Bug fix (bug 81, socket.js pnFromLIDUSync(), critical): the condition inside this function's loop was inverted β€” it warned and skipped every jid where isLidUser(jid) is true, which is every jid this function is ever called with (onWhatsApp() only ever passes it jids it already confirmed are LIDs, specifically to resolve them back to a PN). usyncQuery.users was therefore always empty, hitting the early-return on every single call. The entire "bug 7" LIDβ†’PN fallback this function exists for (already claimed fixed in an earlier round) has never actually executed. Fixed the condition. Left .withId(jid) as-is rather than switching to .withLid(jid): every other withLIDProtocol() caller in the fork (messages-send.js, chats.js, username.js) uses .withId() exclusively, and messages-send.js carries its own long-standing // todo: investigate - the idea here is that <user> should have an inline lid field comment right next to its own .withId() call β€” i.e. UsyncLIDProtocol.getUserElement() checking user.lid (which no caller anywhere ever sets) may itself be a latent, fork-wide issue spanning multiple call sites. That's a live-protocol question, not something safe to resolve with a guess in one function; fixed only the unambiguous self-contradiction.
  • Bug fix (bug 82, socket.js end()): same silent-catch pattern as bugs 70/73 β€” a ws.close() failure during shutdown was swallowed with an empty catch {}. Now logged at trace level (not warn/error, since this fires on every disconnect including expected ones).
  • Bug fix (bug 83, socket.js, dead code): a keyAge line inside the periodic signed-pre-key-rotation block (itself from an earlier fix, bug 37) computed creds.signedPreKey.keyId * 0 β€” multiplying by 0 always yields 0 regardless of the actual keyId, so keyAge was always just Date.now(), and more importantly it was never read anywhere below; the actual rotation check has only ever used lastRotation. Harmless (not a behavior bug) but nonsensical and misleading β€” removed.
  • Bug fix (bug 84, socket.js CB:success handler): ev.emit('creds.update', { me: { ...authState.creds.me, lid: node.attrs.lid } }) unconditionally set me.lid from node.attrs.lid, even when that attribute is absent on this particular success stanza β€” which would silently overwrite a previously-stored, valid me.lid with undefined on any reconnect where the server doesn't repeat it. The code immediately below this line already guards its own LID-mapping/session-migration logic with if (node.attrs.lid && ...), i.e. the attribute was already known to be sometimes missing β€” this line just didn't get the same guard. Now falls back to the existing value instead of clobbering it.
    • Read socket.js (1040 lines) and chats.js (1328 lines) fully, line by line, front to back β€” not sampled. Everything else in both files (query/timeout plumbing, keepalive, pairing/QR, onWhatsApp/USync query building, app-state resync state machine, appPatch's key-wait/resync-debounce logic, presence/profile/privacy queries, the history-sync state machine in upsertMessage) checked out consistent with established, already-audited patterns.
  • Bug fix (bug 80, newsletter.js): newsletterFetchMessages()'s catch block called logger.error(...), but logger was never imported or destructured anywhere in this file β€” a bare undefined reference. Every time a newsletter message failed to decode, this catch block (meant to log the failure and keep processing the rest of the batch) threw its own ReferenceError instead, which propagated out of the whole for loop and rejected the entire newsletterFetchMessages() call β€” losing every already-decoded message in the batch, not just the one that failed. Fixed by destructuring logger from sock (every layer under makeGroupsSocket() already exposes it).
  • Bug fix (bug 79, username.js): deleteUsername() shares USERNAME_QUERY_IDS.SET with setUsername() β€” it's the same GraphQL "UsernameSet" mutation, just called with username: null β€” but used a different dataPath ('xwa2_username_delete' instead of setUsername()'s 'xwa2_username_set'). The server's response is keyed by the query itself, not by whatever a caller names it, so no response would ever contain data under xwa2_username_delete; the mutation would succeed server-side but deleteUsername() would always throw "unexpected response structure" back in executeWMexQuery(). Fixed to match setUsername()'s data path.
    • Read through groups.js (387 lines) and business.js (379 lines) in full and mex.js (41 lines): all faithful to established patterns, nothing else found.
  • Bug fix (bug 78, UsyncBotProfileProtocol.js): every other protocol parser in lib/WAUSync/Protocols/ (contact, status, disappearing_mode, lid, username) receives the tag-matching node directly from USyncQuery.parseUSyncQueryResult() and checks node.tag === '<name>'. This one instead did getBinaryNodeChild(node, 'bot'), searching for a nested <bot> child inside a node that already is the <bot> element β€” always undefined, since getBinaryNodeChild() is null-safe and just returns [] internally rather than throwing. Every field downstream of that (profile, commands, prompts, name, description, category...) silently came back empty on every bot-profile USync response, which is exactly why it went unnoticed instead of crashing. Fixed to read profile straight off node, matching the sibling protocols.
    • Read through the rest of lib/WAUSync/ (USyncQuery.js's query building/result parsing, USyncUser.js, and the remaining 5 protocol files): all faithful to the established pattern, nothing else found.
  • Bug fix (bug 77, lid-mapping.js + libsignal.js): two separate LRUCache instances (mappingCache for PN↔LID pairs, migratedSessionCache for the bulk session-migration cache) both had ttl: 3 * 24 * 60 * 60 * 1000 (259200000ms, 3 days) sitting next to a // 7 days comment β€” the literal should have been 7 * 24 * 60 * 60 * 1000 (604800000ms). Not a security/correctness break (the fallback is just re-fetching from USync/re-checking sessions a bit more often than intended), but real drift between code and its own documented intent, copy-pasted into two places. Fixed both to match the stated 7-day intent.
    • Read through the rest of lib/Signal/ line by line (libsignal.js's migration/storage logic, lid-mapping.js's lookup/cache logic, and all of lib/Signal/Group/ β€” sender key state/record/chain-key/message-key/distribution-message, the group cipher, and the session builder): the actual Signal-protocol crypto logic (key derivation, chain-key ratcheting, sender-key state limits, TOFU identity handling) matches known-correct reference implementations faithfully. Nothing else there rises to a confident finding β€” this is exactly the kind of code where guessing wrong is worse than not touching it, so anything short of a clear discrepancy against the spec was left alone rather than "fixed" speculatively.
  • Bug fix (bug 75, LID/PN gap): generateWAMessageFromContent() always used the PN-form userJid for the participant field (both when quoting our own message, and when stamping our own outgoing group/broadcast messages) even when the target chat is LID-addressed β€” the two spots explicitly marked // TODO: Add support for LIDs. creds.me.lid already exists and is used consistently elsewhere in the fork (messages-recv.js) for exactly this PN-vs-LID selection based on isLidUser(jid), so this wires the same pattern through: a new meParticipant(targetJid) helper in messages.js picks userJidLid over userJid when isLidUser(targetJid) is true, and falls back to userJid when the caller didn't supply userJidLid (e.g. pre-LID-migration accounts) β€” no existing caller breaks. Wired userJidLid (from creds.me?.lid) through all 4 generateWAMessage()/generateWAMessageFromContent() call sites in messages-send.js's sendMessage(), all 8 in dugong.js, and button-sender.js's userJid derivation β€” all three had the identical PN-only derive-from-creds.me.id pattern.
    • Bug fix (bug 76, found while wiring the above): media-set.js's groupStatus()/groupStatusV2() called generateWAMessageFromContent(jid, {...}, {}) with a completely empty options object β€” userJid was never passed at all, so the outgoing group status's participant field was always undefined, independent of LID or not. Now derives and passes both userJid/userJidLid from sock.authState.creds.me, same as the other call sites.
    • Builder classes (Button.js, ButtonV2.js, ButtonV3.js, AIRich.js, Carousel.js, A2UI.js) were checked too β€” they pass through whatever options the caller supplies rather than deriving userJid from creds.me.id themselves, so they're not the same bug; it's on the consumer to pass userJidLid if they want LID-correct participants there.
    • Deliberately left alone (genuine protocol-behavior TODOs, not mechanical wiring gaps β€” static code review can't safely resolve these without live-testing against WhatsApp's servers): messages-recv.js's // TODO: HANDLE PARTICIPANT_PN in the w:gp2 notification path, // TODO: investigate sending receipts to LIDs and not PNs in the read-receipt path (already has a working fallback), and messages-send.js's // TODO: LID MAP this stuff in the USync device-list handling. Guessing wrong on any of these risks silently breaking receipts or group notifications.
  • Bug fix (auth-utils.js commitWithRetry(), bug 71): transaction commits wrote straight to state.set(mutations), completely bypassing the per-type getQueue(type) serialization that the non-transactional set() path already used. A transaction committing type 'session' (or 'pre-key', or anything) could hit the store at the same instant as an unrelated direct set() call for that same type β€” nothing synchronized the two paths against each other. Same root-cause class as bug 66, just not scoped to pre-key this time. Fixed by routing commits through the same per-type queues via a new withTypeQueues() helper (nested PQueue.add() in a fixed sorted key order, so overlapping-type commits can't deadlock each other). Added a regression test (tests/auth-utils.transaction-queue.test.js) plus an offline, dependency-free sanity check of the locking algorithm itself, since this sandbox has no network access to install the fork's own deps (p-queue, async-mutex) and run the real suite β€” flagged as unverified against the actual fork code until someone runs npm test in an environment with those installed.
  • Security fix (bugs 72/74): use-multi-file-auth-state.js and use-single-file-auth-state.js wrote every credential/session file (writeFile) and created the session folder (mkdir) with Node's default permissions (0o644 / 0o755 minus umask) β€” world-readable/listable on any shared box. Now { mode: 0o600 } for files, { mode: 0o700 } for the folder.
  • Silent-catch fixes (bug 73): use-multi-file-auth-state.js's readData() and removeData() swallowed all errors identically to "key doesn't exist" (same pattern as bug 70) β€” now only ENOENT stays silent, anything else (EACCES, corrupt JSON, disk error) is logged.
  • Bug fix (shared.js extractIE()): the hyperlink branch's plain-text fallback wrapped the raw url instead of raw (the label between [label](url)), so any client not applying inline_entities β€” including the plain messageText fallback every submessage carries β€” showed the URL instead of the label for every labeled link (the common case). inline_entities' own display_name: ie.text was already correct; only the plain-text substitution was wrong. Fixed to use raw. [](url) bare citations are unaffected (no label to lose there).
  • Logger consistency (bugs 67-70): swept the fork for leftover console.error/console.log where a logger was already in scope (or one import away) and swapped them: communities.js's parseGroupResult() catch (bug 67, logger already used elsewhere in the same function), make-in-memory-store.js's labels.association unknown-type branch (bug 68, logger already in closure scope via config.logger), and use-cache-manager-auth-state.js's removeData() catch (bug 69, added the logger.js import β€” it's a leaf module wrapping pino, no circular-import risk). Also found and fixed a genuine silent-catch bug while in there: readData()'s catch block (bug 70) swallowed all errors and returned null β€” indistinguishable whether the key genuinely didn't exist yet or store.get()/JSON parse failed (e.g. a dropped Redis connection), the same pattern already fixed in use-single-file-auth-state.js's flushKey(). Now logged; still returns null so initAuthCreds() et al. don't crash on a missing key. worker-bootstrap.js's console.error calls were deliberately left alone β€” that file runs inside an isolated worker_threads context without access to the main logger instance, so console.error there is the correct, intentional choice.
  • Compared AIRich.js against a reference Go builder (NIXCODE/Nixel, whatsmeow-based, AIRich-only) and closed the gaps found:
    • Bug fix: _title/_footer were read by build() (messageDisclaimerText / footer section) but never initialized and had no setter β€” setTitle()/setFooter() plain didn't exist. Both added.
    • New setters: setMentions(), setQuoted(), setForwardBotJID(), setDynamicIDs(), setNoContextInfo(), setUnifiedResponseData(), setVerificationMetadata(), setThreadType(), setImagineType(), setSubscriptionUpsell(), setCapabilities(), setBotRenderingConfig(), setMessageSecret(), setUnsupportedTypeAlert() (previously constructor-option-only), setByPassDownload() (opt-in auto-sendEdit()-after-send() render workaround).
    • New primitives/helpers: addFile() (GenAIFilePrimitive), addHTML()/addHTMLCard() (new GenAIaeacdsnwHtmlPrimitive webview card, optional gzip+base64 for large documents), addComment(), addActionList(), addTasks() (batch addTask()), addChainOfThoughtStep(), addAddonAction() (GenAIAddonActionLayoutViewModel), lastMessageKey getter, buildEditNative()/sendEditNative() (minimal "ala metaai.log" edit envelope, alongside the existing buildEdit()/sendEdit()).
    • A few reference methods were deliberately not ported β€” already covered by existing methods under a different API shape (AddThinkingFull/AddBotThinking β†’ addProgressStatus()/addThinkingStatus(); the AddQuotaUpsell* variants β†’ addQuotaUpsell()'s flexible data object; Opts/OptsAt/DeleteSection/Items β†’ the {id, insertAt, replace} block-tracking mechanism / delete() / the items getter; Defer β†’ pushing Promises straight into _sections/_submessages, already awaited by build()).
    • A couple of defaults were deliberately kept opposite of the Go reference to avoid changing this fork's existing behavior: setDynamicIDs() defaults OFF (Go defaults ON, which would silently break setResponseId()/setBotResponseId() pinning), setVerificationMetadata() defaults ON (matches this fork's pre-existing always-signed behavior), setByPassDownload() defaults OFF (Go defaults ON, which would double every send() call for existing callers).
    • Bumped MESSAGE_BUILDER_VERSION to 4.9.7.
  • Bumped MESSAGE_BUILDER_VERSION to 4.9.3.
  • Reinstated AIRich.addImageCard(previewUrl, fullUrl, { resolveUrl }) β€” removed in the 25-08-26 patch for using an invented, client-crashing schema. Rebuilt on the same gridImageMetadata + GenAIImaginePrimitive layout already proven by addImage(), just with independent preview/full-res urls.

πŸ“¦ Package Contents

The published package is intentionally limited to the runtime library, generated WhatsApp protocol bindings, installation/runtime helpers, legal files, and documentation. The files field in package.json controls what is included in the published package.

@vanzxy/baileys/
β”œβ”€β”€ lib/                     # Main runtime implementation and TypeScript declarations
β”‚   β”œβ”€β”€ Builders/            # Message and interactive builders
β”‚   β”œβ”€β”€ Defaults/            # Default configuration and constants
β”‚   β”œβ”€β”€ Signal/              # Authentication and Signal-related implementation
β”‚   β”œβ”€β”€ Socket/              # WhatsApp Web socket/runtime layer
β”‚   β”œβ”€β”€ Store/               # Store implementations and helpers
β”‚   β”œβ”€β”€ Types/               # Public and internal type definitions
β”‚   β”œβ”€β”€ Utils/               # Runtime utilities
β”‚   β”œβ”€β”€ WABinary/            # Binary node encoding/decoding helpers
β”‚   β”œβ”€β”€ WAM/                 # WhatsApp Web protocol helpers
β”‚   β”œβ”€β”€ WAUSync/             # USync/query functionality
β”‚   β”œβ”€β”€ index.js             # Main package entrypoint
β”‚   └── index.d.ts           # Main TypeScript entrypoint
β”œβ”€β”€ WAProto/                 # Generated WhatsApp protobuf runtime + declarations
β”œβ”€β”€ engine-requirements.js   # Node engine requirement check
β”œβ”€β”€ VanzxyBanner.js          # Package/startup banner helper
β”œβ”€β”€ NOTICE.md                # Third-party attribution and notices
β”œβ”€β”€ LICENSE                  # MIT license
└── README.md                # Project documentation

The package entry points are defined by package.json:

{
  "type": "module",
  "main": "./lib/index.js",
  "module": "./lib/index.js",
  "types": "./lib/index.d.ts"
}

The package also exposes the generated WAProto surface and selected lib/* paths through the exports map. Consumers should prefer the documented public exports rather than depending on undocumented internal file paths.

Published files

The current files allowlist is:

lib/**/*
WAProto/**/*
engine-requirements.js
VanzxyBanner.js
NOTICE.md
LICENSE
README.md

This means documentation changes do not modify the runtime implementation. Editing README.md alone does not alter lib/, WAProto/, authentication behavior, socket behavior, or builders.


πŸ—οΈ Architecture Overview

@vanzxy/baileys is organized in layers. Keeping these layers separate makes it easier to extend a builder or protocol surface without unnecessarily changing the transport layer.

Runtime flow

Application / Bot
       β”‚
       β–Ό
Public exports (`lib/index.js`)
       β”‚
       β”œβ”€β”€ Builders ───────────────► Message construction
       β”‚                              AIRich / A2UI / Button / Carousel / Toolkit
       β”‚
       β”œβ”€β”€ Socket ─────────────────► Connection + send/receive lifecycle
       β”‚                              auth state / reconnect / message send
       β”‚
       β”œβ”€β”€ Signal ─────────────────► Session + cryptographic state
       β”‚
       β”œβ”€β”€ Store ──────────────────► Message / auth / metadata persistence
       β”‚
       β”œβ”€β”€ WABinary / WAM ─────────► WhatsApp node and protocol helpers
       β”‚
       └── WAUSync ────────────────► User/business/query synchronization
       β”‚
       β–Ό
WAProto
       β”‚
       β–Ό
WhatsApp Web protocol messages

lib/Builders/

This layer contains the higher-level message construction APIs. It is where the fork's interactive and rich-message work is concentrated. Builders are designed to turn JavaScript objects and fluent method calls into WhatsApp message structures before the socket layer transmits them.

Important builder families documented by this package include AIRich, A2UI, button builders, carousel support, and the shared toolkit/helpers used by those builders.

lib/Socket/

The socket layer is responsible for the connection-facing side of the library: establishing and maintaining the WhatsApp Web session, receiving nodes/events, sending messages, and coordinating runtime state.

Application code should normally interact with this layer through the public socket factory and exported helpers instead of reaching into socket implementation files directly.

lib/Signal/

Signal-related modules handle the cryptographic/session state required for multi-device communication. Changes here should be treated as high-impact changes: a seemingly small alteration can affect authentication, session establishment, message decryption, or group sender-key behavior.

lib/Store/

Store modules provide persistence abstractions and helpers used by the runtime. The exact backend can vary depending on the application and the optional dependencies installed alongside the package.

lib/WABinary/, lib/WAM/, and lib/WAUSync/

These lower-level modules provide protocol-facing utilities. They are useful when implementing advanced functionality, but application code should use higher-level public helpers where possible.

WAProto/

WAProto contains the generated protocol definitions used to construct and parse WhatsApp message structures. It is part of the published package and is exposed separately through the package exports.


🧭 Public Entry Points & Module Map

The main declaration entry point re-exports the principal package surfaces so TypeScript consumers can resolve the same API family as JavaScript consumers.

lib/index.d.ts
β”œβ”€β”€ WAProto
β”œβ”€β”€ Utils
β”œβ”€β”€ Builders
β”œβ”€β”€ Types
β”œβ”€β”€ Store
β”œβ”€β”€ Defaults
β”œβ”€β”€ WABinary
β”œβ”€β”€ WAM
β”œβ”€β”€ WAUSync
β”œβ”€β”€ Dugong
β”œβ”€β”€ makeWASocket
└── exports-extra

The package also defines these export paths in package.json:

| Export | Purpose | |---|---| | @vanzxy/baileys | Main public API and socket factory | | @vanzxy/baileys/WAProto | Generated protocol API | | @vanzxy/baileys/WAProto/* | Individual generated protocol modules | | @vanzxy/baileys/lib/* | Published library modules with declarations | | @vanzxy/baileys/package.json | Package metadata |

For normal application development, the root import is the safest starting point:

import makeWASocket, { AIRich, DisconnectReason } from '@vanzxy/baileys'

If a specialized module is required, use an export path explicitly documented by the package rather than importing a source file that is not part of the export map.


🧩 Feature Surface Matrix

The following matrix describes the documented surface of this fork without treating every feature as equally stable or equally supported by every WhatsApp client version.

| Area | Surface | Notes | |---|---|---| | Connection | Multi-device socket | Main runtime surface | | Authentication | Auth state / Signal | Session state is security-sensitive | | Messages | Standard message generation | Core Baileys-style flow | | Media | Image/audio/video/document helpers | Optional codecs/tools may be required | | Interactive | Buttons / native-flow related builders | Client rendering can vary | | Rich messages | AIRich | Extended builder surface | | UI | A2UI | Advanced interactive/UI structures | | Carousel | Carousel builder/support | Client capability dependent | | Polls | Poll construction | Rendering/support depends on message type and client | | USync | User/business/query helpers | Protocol-facing API | | LID | LID-aware participant handling | Important for modern WhatsApp addressing | | TypeScript | .d.ts declarations | Included in the package | | Protocol | WAProto | Generated protobuf surface | | Stores | Store helpers/backends | Some backends use optional dependencies | | Calls | Voice/video call helpers | Runtime/client capability dependent |

Compatibility note: a function being present in the JavaScript or TypeScript API does not guarantee that every WhatsApp client version will render or accept every advanced message structure. For experimental/native structures, verify behavior against the client versions you actually target.


πŸ”„ Migration Notes for v2.0.4

When moving an application to @vanzxy/baileys v2.0.4, check the following areas first:

  1. Runtime: use Node.js >=20.0.0.
  2. Module system: the package is ESM-only ("type": "module"). Use import/export rather than CommonJS require() for package modules.
  3. Entry point: the package resolves the main runtime through ./lib/index.js and TypeScript declarations through ./lib/index.d.ts.
  4. Protocol imports: WAProto is available through the package export map; avoid assuming an unrelated generated-proto path from another Baileys fork.
  5. Builders: advanced builder APIs such as AIRich and A2UI should be tested against the actual WhatsApp client/device combination used by the application.
  6. LID-aware code: applications that manually construct quoted messages, participants, or group/status payloads should verify that their code does not blindly assume PN-only addressing.
  7. Optional dependencies: media, database, WebRTC, preview, and other optional capabilities may require their corresponding peer dependency to be installed.
  8. Lifecycle scripts: preinstall performs the engine requirement check, while prestart invokes the Vanzxy banner when using the package's npm start lifecycle. These lifecycle hooks are not substitutes for a process manager's startup command.

Common ESM migration mistakes

// ❌ CommonJS in an ESM-only application
const baileys = require('@vanzxy/baileys')

// βœ… ESM
import makeWASocket from '@vanzxy/baileys'

For dynamic loading, use import():

const module = await import('@vanzxy/baileys')

πŸ§ͺ Verification & Test Strategy

Advanced changes should be tested at the layer they modify rather than relying on one end-to-end send test to prove everything works.

Recommended verification order

1. Static/import check
        ↓
2. Type/declaration check
        ↓
3. Builder construction test
        ↓
4. Serialization/protobuf test
        ↓
5. Relay/send test
        ↓
6. Real-client rendering test

For example, an AIRich change should first prove that the builder produces the expected structure before diagnosing a client-rendering issue as a builder bug. Likewise, a protocol change should be checked at the protobuf/node level before adding retries or transport workarounds.

What a useful bug report contains

Package version: @vanzxy/baileys x.y.z
Node.js version: v20+ / exact version
Environment: Linux / Pterodactyl / Termux / VPS / other
Feature: AIRich / A2UI / media / socket / auth / etc.
Expected: ...
Actual: ...
Reproduction: minimal code or command
Stack trace: ...
WhatsApp client/device: ...

For authentication or connection failures, include the relevant disconnect reason and a sanitized log. Never publish credentials, session files, private keys, or authentication state in an issue.


πŸ› οΈ Troubleshooting Guide

require() cannot be used on an ESM graph

The package is ESM-only. Replace CommonJS require() usage with static import or dynamic import() as appropriate.

ERR_MODULE_NOT_FOUND

Check all of the following:

  • The dependency is installed in the same runtime where the bot is running.
  • The import path inclu