@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.
Maintainers
Keywords
Readme
- π Why @vanzxy/baileys
- π Comparison
- π§° Built With
- π₯ Features
- β¨ Exclusive Vanzxy Enhancements
- π¦ Installation
- π Quick Start
- π Authentication
- ποΈ Store Backends
- π‘ Usage Examples
- π Voice & Video Calls
- π User Sync Queries
- π€ Username Management
- π€ Bot Framework
- π§© Utility Modules
- ποΈ System Notification Filter
- π Recommended Environment
- π TypeScript Support
- π Fork Lineage
- β FAQ & Troubleshooting
- π€ Contributing
- π Credits
- π Repo Activity
- π Support
- π Maintainer
- β οΈ Disclaimer
π Maintainer
Vanzxy
- GitHub: github.com/vanzxysenpai
- Package:
@vanzxy/baileys - β¦ Maintains this fork solo β issues & PRs are welcome and reviewed personally.
π 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.isSystemNotificationUseful for filtering:
- E2E notices
- Meta service notices
- Other system-generated messages
π¦ Installation
npm install @vanzxy/baileysDirectly from GitHub:
npm install github:vanzxysenpai/vanzxybaileysRequires Node.js 20+ β install will refuse to run below that (checked by
engine-requirements.jsonpreinstall).
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
INVALIDor 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
.jsfor now β hand-written.d.tsdeclarations 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/BaileysRespect 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.
- Fork repo ini, buat branch dari
main(feat/nama-fiturataufix/nama-bug) - Pastikan perubahan tetap ESM-only dan menyertakan/menyesuaikan
.d.tsterkait - Uji perubahan pada minimal satu jalur auth state + satu store backend sebelum PR
- 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()returnednew 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)takesclientas its first argument, while every sibling factory off this hub (button(),buttonV2(),buttonV3(),carousel(),poll()) pre-binds#clientat construction and exposessend(jid, opts). Callinghub.a2ui().send(jid, opts)the same way you'd call any other builder off the hub silently passesjidasclientandoptsasjidβ exactly the inconsistency this "unified builder hub" exists to prevent. Fixed by wrapping the returned instance'ssend()to pre-currythis.client, so it matches the hub's own calling convention; the standalonenew 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, andButton.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()callingButton.setTitle()/.setBody(), which turned out to be correctly inherited fromBaseBuilder).AIRich.js(4090 lines, by far the most heavily audited file in the fork across prior sessions β see the file's own extensiveVanz@Fix/Vanz@Addhistory) was sampled rather than read exhaustively: the constructor, thebuild()/send()/buildEdit()/sendEdit()integration tail, and a cross-section ofadd*()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.
- Read
- Bug fix (bug 81,
socket.jspnFromLIDUSync(), critical): the condition inside this function's loop was inverted β it warned and skipped every jid whereisLidUser(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.userswas 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 otherwithLIDProtocol()caller in the fork (messages-send.js,chats.js,username.js) uses.withId()exclusively, andmessages-send.jscarries its own long-standing// todo: investigate - the idea here is that <user> should have an inline lid fieldcomment right next to its own.withId()call β i.e.UsyncLIDProtocol.getUserElement()checkinguser.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.jsend()): same silent-catch pattern as bugs 70/73 β aws.close()failure during shutdown was swallowed with an emptycatch {}. Now logged attracelevel (notwarn/error, since this fires on every disconnect including expected ones). - Bug fix (bug 83,
socket.js, dead code): akeyAgeline inside the periodic signed-pre-key-rotation block (itself from an earlier fix, bug 37) computedcreds.signedPreKey.keyId * 0β multiplying by 0 always yields 0 regardless of the actualkeyId, sokeyAgewas always justDate.now(), and more importantly it was never read anywhere below; the actual rotation check has only ever usedlastRotation. Harmless (not a behavior bug) but nonsensical and misleading β removed. - Bug fix (bug 84,
socket.jsCB:successhandler):ev.emit('creds.update', { me: { ...authState.creds.me, lid: node.attrs.lid } })unconditionally setme.lidfromnode.attrs.lid, even when that attribute is absent on this particular success stanza β which would silently overwrite a previously-stored, validme.lidwithundefinedon any reconnect where the server doesn't repeat it. The code immediately below this line already guards its own LID-mapping/session-migration logic withif (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) andchats.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 inupsertMessage) checked out consistent with established, already-audited patterns.
- Read
- Bug fix (bug 80,
newsletter.js):newsletterFetchMessages()'s catch block calledlogger.error(...), butloggerwas 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 ownReferenceErrorinstead, which propagated out of the wholeforloop and rejected the entirenewsletterFetchMessages()call β losing every already-decoded message in the batch, not just the one that failed. Fixed by destructuringloggerfromsock(every layer undermakeGroupsSocket()already exposes it). - Bug fix (bug 79,
username.js):deleteUsername()sharesUSERNAME_QUERY_IDS.SETwithsetUsername()β it's the same GraphQL "UsernameSet" mutation, just called withusername: nullβ but used a differentdataPath('xwa2_username_delete'instead ofsetUsername()'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 underxwa2_username_delete; the mutation would succeed server-side butdeleteUsername()would always throw "unexpected response structure" back inexecuteWMexQuery(). Fixed to matchsetUsername()'s data path.- Read through
groups.js(387 lines) andbusiness.js(379 lines) in full andmex.js(41 lines): all faithful to established patterns, nothing else found.
- Read through
- Bug fix (bug 78,
UsyncBotProfileProtocol.js): every other protocol parser inlib/WAUSync/Protocols/(contact, status, disappearing_mode, lid, username) receives the tag-matching node directly fromUSyncQuery.parseUSyncQueryResult()and checksnode.tag === '<name>'. This one instead didgetBinaryNodeChild(node, 'bot'), searching for a nested<bot>child inside a node that already is the<bot>element β alwaysundefined, sincegetBinaryNodeChild()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 readprofilestraight offnode, 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.
- Read through the rest of
- Bug fix (bug 77,
lid-mapping.js+libsignal.js): two separateLRUCacheinstances (mappingCachefor PNβLID pairs,migratedSessionCachefor the bulk session-migration cache) both hadttl: 3 * 24 * 60 * 60 * 1000(259200000ms, 3 days) sitting next to a// 7 dayscomment β the literal should have been7 * 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 oflib/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.
- Read through the rest of
- Bug fix (bug 75, LID/PN gap):
generateWAMessageFromContent()always used the PN-formuserJidfor theparticipantfield (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.lidalready exists and is used consistently elsewhere in the fork (messages-recv.js) for exactly this PN-vs-LID selection based onisLidUser(jid), so this wires the same pattern through: a newmeParticipant(targetJid)helper inmessages.jspicksuserJidLidoveruserJidwhenisLidUser(targetJid)is true, and falls back touserJidwhen the caller didn't supplyuserJidLid(e.g. pre-LID-migration accounts) β no existing caller breaks. WireduserJidLid(fromcreds.me?.lid) through all 4generateWAMessage()/generateWAMessageFromContent()call sites inmessages-send.js'ssendMessage(), all 8 indugong.js, andbutton-sender.js'suserJidderivation β all three had the identical PN-only derive-from-creds.me.idpattern.- Bug fix (bug 76, found while wiring the above):
media-set.js'sgroupStatus()/groupStatusV2()calledgenerateWAMessageFromContent(jid, {...}, {})with a completely empty options object βuserJidwas never passed at all, so the outgoing group status'sparticipantfield was alwaysundefined, independent of LID or not. Now derives and passes bothuserJid/userJidLidfromsock.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 whateveroptionsthe caller supplies rather than derivinguserJidfromcreds.me.idthemselves, so they're not the same bug; it's on the consumer to passuserJidLidif 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_PNin thew:gp2notification path,// TODO: investigate sending receipts to LIDs and not PNsin the read-receipt path (already has a working fallback), andmessages-send.js's// TODO: LID MAP this stuffin the USync device-list handling. Guessing wrong on any of these risks silently breaking receipts or group notifications.
- Bug fix (bug 76, found while wiring the above):
- Bug fix (
auth-utils.jscommitWithRetry(), bug 71): transaction commits wrote straight tostate.set(mutations), completely bypassing the per-typegetQueue(type)serialization that the non-transactionalset()path already used. A transaction committing type'session'(or'pre-key', or anything) could hit the store at the same instant as an unrelated directset()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 newwithTypeQueues()helper (nestedPQueue.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 runsnpm testin an environment with those installed. - Security fix (bugs 72/74):
use-multi-file-auth-state.jsanduse-single-file-auth-state.jswrote 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'sreadData()andremoveData()swallowed all errors identically to "key doesn't exist" (same pattern as bug 70) β now onlyENOENTstays silent, anything else (EACCES, corrupt JSON, disk error) is logged. - Bug fix (
shared.jsextractIE()): the hyperlink branch's plain-text fallback wrapped the rawurlinstead ofraw(the label between[label](url)), so any client not applyinginline_entitiesβ including the plainmessageTextfallback every submessage carries β showed the URL instead of the label for every labeled link (the common case).inline_entities' owndisplay_name: ie.textwas already correct; only the plain-text substitution was wrong. Fixed to useraw.[](url)bare citations are unaffected (no label to lose there). - Logger consistency (bugs 67-70): swept the fork for leftover
console.error/console.logwhere aloggerwas already in scope (or one import away) and swapped them:communities.js'sparseGroupResult()catch (bug 67,loggeralready used elsewhere in the same function),make-in-memory-store.js'slabels.associationunknown-type branch (bug 68,loggeralready in closure scope viaconfig.logger), anduse-cache-manager-auth-state.js'sremoveData()catch (bug 69, added thelogger.jsimport β 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 returnednullβ indistinguishable whether the key genuinely didn't exist yet orstore.get()/JSON parse failed (e.g. a dropped Redis connection), the same pattern already fixed inuse-single-file-auth-state.js'sflushKey(). Now logged; still returnsnullsoinitAuthCreds()et al. don't crash on a missing key.worker-bootstrap.js'sconsole.errorcalls were deliberately left alone β that file runs inside an isolatedworker_threadscontext without access to the mainloggerinstance, soconsole.errorthere is the correct, intentional choice. - Compared
AIRich.jsagainst a reference Go builder (NIXCODE/Nixel, whatsmeow-based, AIRich-only) and closed the gaps found:- Bug fix:
_title/_footerwere read bybuild()(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()(newGenAIaeacdsnwHtmlPrimitivewebview card, optional gzip+base64 for large documents),addComment(),addActionList(),addTasks()(batchaddTask()),addChainOfThoughtStep(),addAddonAction()(GenAIAddonActionLayoutViewModel),lastMessageKeygetter,buildEditNative()/sendEditNative()(minimal "ala metaai.log" edit envelope, alongside the existingbuildEdit()/sendEdit()). - A few reference methods were deliberately not ported β already covered by existing methods under a different API shape (
AddThinkingFull/AddBotThinkingβaddProgressStatus()/addThinkingStatus(); theAddQuotaUpsell*variants βaddQuotaUpsell()'s flexible data object;Opts/OptsAt/DeleteSection/Itemsβ the{id, insertAt, replace}block-tracking mechanism /delete()/ theitemsgetter;Deferβ pushing Promises straight into_sections/_submessages, already awaited bybuild()). - 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 breaksetResponseId()/setBotResponseId()pinning),setVerificationMetadata()defaults ON (matches this fork's pre-existing always-signed behavior),setByPassDownload()defaults OFF (Go defaults ON, which would double everysend()call for existing callers). - Bumped
MESSAGE_BUILDER_VERSIONto4.9.7.
- Bug fix:
- Bumped
MESSAGE_BUILDER_VERSIONto4.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 samegridImageMetadata+GenAIImaginePrimitivelayout already proven byaddImage(), 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 documentationThe 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.mdThis 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 messageslib/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-extraThe 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:
- Runtime: use Node.js
>=20.0.0. - Module system: the package is ESM-only (
"type": "module"). Useimport/exportrather than CommonJSrequire()for package modules. - Entry point: the package resolves the main runtime through
./lib/index.jsand TypeScript declarations through./lib/index.d.ts. - Protocol imports:
WAProtois available through the package export map; avoid assuming an unrelated generated-proto path from another Baileys fork. - Builders: advanced builder APIs such as
AIRichandA2UIshould be tested against the actual WhatsApp client/device combination used by the application. - 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.
- Optional dependencies: media, database, WebRTC, preview, and other optional capabilities may require their corresponding peer dependency to be installed.
- Lifecycle scripts:
preinstallperforms the engine requirement check, whileprestartinvokes the Vanzxy banner when using the package'snpm startlifecycle. 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 testFor 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
