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

bera-baileys

v2.0.0

Published

A WebSockets library for interacting with WhatsApp Web

Readme

bera-baileys

bera-baileys is a lightweight, no-browser TypeScript/JavaScript library for interacting with the WhatsApp Web multi-device API. It is a rebranded fork of the excellent Baileys project, with an added built-in module for sending interactive button and list messages.

This library connects directly to WhatsApp's WebSocket servers — no Selenium, Puppeteer, or headless browser required.

Table of contents

Features

  • Connect to WhatsApp Web via QR code or pairing code
  • Send and receive text, image, video, audio, document, and sticker messages
  • Send interactive button and list messages (quick replies, URL buttons, call buttons, copy-code buttons, and list menus)
  • Group management (create, add/remove participants, promote/demote, settings)
  • Community and newsletter (channel) support
  • Read receipts, presence updates, typing indicators
  • Multi-device support with in-memory or file-based auth state persistence
  • Polls, reactions, ephemeral/disappearing messages, message edits & deletes
  • Written entirely in TypeScript with full type definitions

Install

npm install bera-baileys

or with yarn/pnpm:

yarn add bera-baileys
# or
pnpm add bera-baileys

bera-baileys has a few optional peer dependencies depending on what you use:

npm install jimp link-preview-js sharp audio-decode

| Package | Needed for | |---|---| | jimp / sharp | Auto-generated image thumbnails | | link-preview-js | Auto-generated link previews | | audio-decode | Voice-note waveform generation |

Quick start

import makeWASocket, { useMultiFileAuthState, DisconnectReason } from 'bera-baileys'
import { Boom } from '@hapi/boom'

async function startSock() {
	// persists login credentials to a local folder so you don't have to re-scan the QR code every run
	const { state, saveCreds } = await useMultiFileAuthState('auth_info_baileys')

	const sock = makeWASocket({
		auth: state,
		printQRInTerminal: true // scan this QR code with WhatsApp > Linked Devices
	})

	sock.ev.on('creds.update', saveCreds)

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

		if (connection === 'close') {
			const shouldReconnect =
				(lastDisconnect?.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut

			console.log('connection closed, reconnecting:', shouldReconnect)
			if (shouldReconnect) startSock()
		} else if (connection === 'open') {
			console.log('connected to WhatsApp!')
		}
	})

	sock.ev.on('messages.upsert', async ({ messages, type }) => {
		if (type !== 'notify') return

		for (const msg of messages) {
			if (!msg.key.fromMe && msg.message) {
				console.log('received message:', msg.message)

				// echo it back
				await sock.sendMessage(msg.key.remoteJid!, { text: 'Hey there! 👋' })
			}
		}
	})

	return sock
}

startSock()

Run it, scan the printed QR code with WhatsApp → Linked Devices → Link a Device, and you're connected.

Logging in with a pairing code instead of a QR code

if (!sock.authState.creds.registered) {
	const phoneNumber = '15551234567' // in international format, no + or spaces
	const code = await sock.requestPairingCode(phoneNumber)
	console.log('Your pairing code:', code)
}

Picking a WhatsApp Web version

import makeWASocket, { fetchLatestBeraBaileysVersion, useMultiFileAuthState } from 'bera-baileys'

const { state } = await useMultiFileAuthState('auth_info_baileys')
const { version, isLatest } = await fetchLatestBeraBaileysVersion()

const sock = makeWASocket({ version, auth: state })

Sending messages

// plain text
await sock.sendMessage(jid, { text: 'Hello there!' })

// reply / quote a message
await sock.sendMessage(jid, { text: 'Hello there!' }, { quoted: someMessage })

// image
await sock.sendMessage(jid, {
	image: { url: './my-image.png' },
	caption: 'Check this out'
})

// video
await sock.sendMessage(jid, { video: fs.readFileSync('./video.mp4'), caption: 'A video!' })

// audio / voice note
await sock.sendMessage(jid, { audio: { url: './audio.mp3' }, ptt: true })

// document
await sock.sendMessage(jid, {
	document: { url: './file.pdf' },
	mimetype: 'application/pdf',
	fileName: 'report.pdf'
})

// sticker
await sock.sendMessage(jid, { sticker: { url: './sticker.webp' } })

// poll
await sock.sendMessage(jid, {
	poll: { name: 'Pick a lunch spot', values: ['Tacos', 'Pizza', 'Sushi'], selectableCount: 1 }
})

// mention someone in a group
await sock.sendMessage(groupJid, {
	text: '@1234567890 check this out',
	mentions: ['[email protected]']
})

// react to a message
await sock.sendMessage(jid, { react: { text: '🔥', key: someMessage.key } })

// edit a message you sent
await sock.sendMessage(jid, { text: 'edited text', edit: someMessage.key })

// delete a message you sent
await sock.sendMessage(jid, { delete: someMessage.key })

jid is the WhatsApp ID of the chat — <number>@s.whatsapp.net for a direct message, or <id>@g.us for a group.

Interactive buttons & lists

bera-baileys ships a dedicated Buttons module for sending interactive quick-reply, URL, call, and copy-code buttons, as well as list/menu messages — something the upstream Baileys library dropped official support for.

Button message

import { sendButtonMessage } from 'bera-baileys'

await sendButtonMessage(sock, jid, {
	text: 'How can I help you today?',
	footer: 'Powered by bera-baileys',
	header: { title: 'Support Menu' },
	buttons: [
		{ type: 'reply', displayText: '📦 Track order', id: 'track_order' },
		{ type: 'reply', displayText: '💬 Talk to a human', id: 'talk_human' },
		{ type: 'url', displayText: '🌐 Visit our site', url: 'https://example.com' },
		{ type: 'call', displayText: '📞 Call us', phoneNumber: '+15551234567' },
		{ type: 'copy', displayText: '📋 Copy discount code', copyCode: 'SAVE20' }
	]
})

Supported button types:

| Type | Fields | Behavior | |---|---|---| | reply | displayText, id | Sends the given id back to your bot as a plain reply when tapped | | url | displayText, url, merchantUrl? | Opens a link in the browser | | call | displayText, phoneNumber | Starts a phone call | | copy | displayText, copyCode | Copies text to the recipient's clipboard |

You can also attach an image header:

await sendButtonMessage(sock, jid, {
	text: 'New arrivals just dropped!',
	header: { imageUrl: 'https://example.com/banner.png', title: 'New Collection' },
	buttons: [{ type: 'url', displayText: 'Shop now', url: 'https://example.com/shop' }]
})

Only building the message (without sending) is also available, e.g. to forward or queue it:

import { generateButtonMessage } from 'bera-baileys'

const message = await generateButtonMessage(sock, {
	text: 'Choose an option',
	buttons: [{ type: 'reply', displayText: 'Option A', id: 'opt_a' }]
})

List message

import { sendListMessage } from 'bera-baileys'

await sendListMessage(sock, jid, {
	text: 'Please choose a department:',
	footer: 'We reply within a few minutes',
	buttonText: 'View departments',
	sections: [
		{
			title: 'Sales',
			rows: [
				{ title: 'New purchase', description: 'Talk to sales about a new order', id: 'sales_new' },
				{ title: 'Existing order', description: 'Questions about an order you placed', id: 'sales_existing' }
			]
		},
		{
			title: 'Support',
			rows: [
				{ title: 'Technical issue', description: 'Something is not working', id: 'support_tech' },
				{ title: 'Billing', description: 'Questions about your invoice', id: 'support_billing' }
			]
		}
	]
})

generateListMessage(sock, options) is the build-only counterpart, mirroring generateButtonMessage.

Reading a button/list reply

Replies come back through the normal messages.upsert event, inside nativeFlowResponseMessage:

sock.ev.on('messages.upsert', ({ messages }) => {
	for (const msg of messages) {
		const response = msg.message?.interactiveResponseMessage?.nativeFlowResponseMessage
		if (response?.paramsJson) {
			const { id } = JSON.parse(response.paramsJson)
			console.log('user tapped button/row with id:', id)
		}
	}
})

Handling incoming events

bera-baileys exposes an event emitter (sock.ev) with granular events, or you can process them in an efficient batch via sock.ev.process:

sock.ev.process(async (events) => {
	if (events['connection.update']) {
		console.log('connection update', events['connection.update'])
	}

	if (events['creds.update']) {
		await saveCreds()
	}

	if (events['messages.upsert']) {
		console.log('new messages', events['messages.upsert'].messages)
	}

	if (events['messages.update']) {
		console.log('messages updated', events['messages.update'])
	}

	if (events['presence.update']) {
		console.log('presence update', events['presence.update'])
	}

	if (events['group-participants.update']) {
		console.log('group membership changed', events['group-participants.update'])
	}
})

Full list of events you can listen for (via sock.ev.on(name, handler) or inside sock.ev.process): connection.update, creds.update, messaging-history.set, chats.upsert, chats.update, chats.delete, presence.update, contacts.upsert, contacts.update, messages.upsert, messages.update, messages.delete, messages.reaction, message-receipt.update, groups.upsert, groups.update, group-participants.update, group.member-tag.update, blocklist.set, blocklist.update, call, labels.association, labels.edit.

Group management

// create a group
const group = await sock.groupCreate('Weekend Trip', ['[email protected]'])

// add / remove / promote / demote participants
await sock.groupParticipantsUpdate(group.id, ['[email protected]'], 'add')
await sock.groupParticipantsUpdate(group.id, ['[email protected]'], 'remove')
await sock.groupParticipantsUpdate(group.id, ['[email protected]'], 'promote')
await sock.groupParticipantsUpdate(group.id, ['[email protected]'], 'demote')

// rename / re-describe
await sock.groupUpdateSubject(group.id, 'New group name')
await sock.groupUpdateDescription(group.id, 'Trip planning happens here')

// settings
await sock.groupSettingUpdate(group.id, 'announcement') // only admins can send messages
await sock.groupToggleEphemeral(group.id, 7 * 24 * 60 * 60) // 7-day disappearing messages

// invite links
const code = await sock.groupInviteCode(group.id)
console.log(`https://chat.whatsapp.com/${code}`)
await sock.groupRevokeInvite(group.id)

// leave a group
await sock.groupLeave(group.id)

// fetch metadata / all groups you're in
const metadata = await sock.groupMetadata(group.id)
const allGroups = await sock.groupFetchAllParticipating()

Auth state persistence

useMultiFileAuthState(folder) is the simplest option — it writes creds/keys to a local folder so a restart doesn't require re-scanning the QR code:

import { useMultiFileAuthState } from 'bera-baileys'

const { state, saveCreds } = await useMultiFileAuthState('auth_info_baileys')
const sock = makeWASocket({ auth: state })
sock.ev.on('creds.update', saveCreds)

For production deployments you typically want to persist to a database instead of the filesystem — implement the AuthenticationState (creds + keys with get/set) shape yourself and pass it as auth. makeCacheableSignalKeyStore(keys, logger) wraps any key store with an in-memory cache, which noticeably speeds up encrypt/decrypt on busy chats:

import { makeCacheableSignalKeyStore } from 'bera-baileys'

const sock = makeWASocket({
	auth: {
		creds: state.creds,
		keys: makeCacheableSignalKeyStore(state.keys, logger)
	}
})

Full example

A complete, runnable connection example (QR/pairing-code login, auto-reconnect, echo bot, poll/history handling) lives in Example/example.ts. Run it with:

npm run example
# or, to log in with a pairing code instead of a QR code:
npm run example -- --use-pairing-code
# to make the bot echo messages back:
npm run example -- --do-reply

API reference

The full generated API reference (types, socket methods, events) can be built locally with:

npm run build:docs

Top-level exports

Everything below is importable directly from 'bera-baileys':

import makeWASocket, {
	// connection / auth
	useMultiFileAuthState,
	makeCacheableSignalKeyStore,
	fetchLatestBeraBaileysVersion,
	DisconnectReason,
	DEFAULT_CONNECTION_CONFIG,

	// message utilities
	generateMessageIDV2,
	getAggregateVotesInPollMessage,
	prepareWAMessageMedia,
	downloadMediaMessage,
	extractMessageContent,

	// interactive buttons & lists
	sendButtonMessage,
	generateButtonMessage,
	sendListMessage,
	generateListMessage,

	// jid helpers
	isJidBroadcast,
	isJidGroup,
	isJidNewsletter,
	isJidUser,
	jidNormalizedUser,

	// protobuf + types
	proto,
	WAMessage,
	WAMessageKey,
	WAMessageContent,
	CacheStore
} from 'bera-baileys'

makeWASocket(config) is the default export and the main entry point — it returns the WASocket instance used for everything else in this document (sock.sendMessage, sock.ev, sock.groupCreate, ...).

Module map

The package re-exports everything from these internal modules (see src/index.ts), so you rarely need to reach into subpaths:

| Module | Contains | |---|---| | WAProto | Generated protobuf types for the WhatsApp wire protocol (proto.Message, proto.WebMessageInfo, ...) | | Utils | useMultiFileAuthState, makeCacheableSignalKeyStore, message building/media download helpers, crypto, JID helpers, link previews, history sync, event buffering | | Types | AuthenticationState, SocketConfig, WAMessage, GroupMetadata, Contact, Chat, events map, DisconnectReason, and every other public type | | Defaults | DEFAULT_CONNECTION_CONFIG and other default values/constants | | WABinary | Low-level binary-node (WhatsApp's XML-like wire format) encode/decode helpers | | WAM | WhatsApp analytics/metrics protobuf helpers | | WAUSync | User-sync protocol helpers (querying contacts' status, business profile, etc.) | | Buttons | sendButtonMessage, sendListMessage, generateButtonMessage, generateListMessage, and their supporting types | | Socket (internal, layered) | makeWASocket itself, composed from groups.ts, chats.ts, messages-send.ts, messages-recv.ts, business.ts, newsletter.ts, communities.ts — these attach sock.groupCreate, sock.sendMessage, sock.chatModify, etc. onto the socket returned by makeWASocket |

Key entry points at a glance:

  • makeWASocket(config) — creates and returns the socket instance
  • useMultiFileAuthState(folder) — file-based credential/session store
  • sock.sendMessage(jid, content, options?) — send any regular message type
  • sendButtonMessage(sock, jid, options) / sendListMessage(sock, jid, options) — send interactive buttons/lists
  • sock.ev — the event emitter for all connection/message/chat events
  • sock.groupCreate, sock.groupParticipantsUpdate, sock.groupUpdateSubject, etc. — group management (see Group management)
  • fetchLatestBeraBaileysVersion() — fetches the latest WhatsApp Web version to connect with

Caveats

WhatsApp does not officially support third-party clients sending interactive button/list messages. The Buttons module works by composing the same binary-node payloads official community workarounds (e.g. gifted-btns, malvin-btns) use, without patching any core Baileys files. This means:

  • It can stop working if WhatsApp changes its client-side validation.
  • Heavy/automated use of interactive messages is a pattern WhatsApp's anti-spam systems actively look for — use responsibly, and avoid this on numbers you cannot afford to lose.

More generally, using any unofficial WhatsApp API (including this one) carries a risk of your number being banned if used for spam, bulk messaging, or other behavior that violates WhatsApp's Terms of Service. Use at your own risk.

Credits

bera-baileys is a rebranded, feature-extended fork of Baileys by WhiskeySockets and its contributors, originally created by Adhiraj Singh. All core WhatsApp Web protocol work is thanks to the upstream project — this fork focuses on the interactive buttons/list module and rebranding.

License

MIT