@crysnovax/baileys
Table of Contents
| # | Section | Description |
|---|---|---|
| 1 | Overview | What this package is and what sets it apart |
| 2 | Installation | NPM, GitHub, ESM & CJS imports |
| 3 | Quick Start | Minimal working connection example |
| 4 | Authentication | Auth states, pairing codes, SQLite |
| 5 | Sending Messages | Text, mentions, reactions, contacts, locations, events, polls, forward, edit |
| 6 | Media Messages | Images, videos, audio, documents, stickers, albums |
| 7 | Interactive Messages | Buttons, lists, native flows, carousels, hydrated templates |
| 8 | Rich Content | Code blocks, tables, inline entities, rich response arrays |
| 9 | Meta AI Features | Meta typing, meta compositing, replay planning |
| 10 | Welcome Flow | Auto-greet new contacts with FAQ menus |
| 11 | Payment Messages | Invites, invoices, orders, payment requests |
| 12 | Message Options | AI icon, ephemeral, spoilers, view once, raw, and more |
| 13 | Status & Stories | Post status updates with text, media, mentions |
| 14 | Favorites | Manage WhatsApp Favorites list |
| 15 | Newsletter Management | Create, update, follow, react, delete newsletters |
| 16 | Group Management | Create, settings, participants, invites, metadata |
| 17 | Community Management | Communities, subgroups, linked groups |
| 18 | Profile & Business | Profile pictures, business catalog, products, quick replies |
| 19 | Privacy & Security | Last seen, online, status, read receipts, calls |
| 20 | Utility Methods | LID mapping, dirty bits, app state sync, props |
| 21 | Events Reference | Full list of socket event listeners |
| 22 | Image Processing | Sharp, NAPI-RS, Jimp auto-detection |
| 23 | Condition of Use | Channel follow agreement |
| 24 | Credits | Attribution and enhancements |
Overview
@crysnovax/baileys is a powerful, production-ready WhatsApp API wrapper for Node.js, built on top of the Baileys protocol. It extends the core with rich messaging capabilities, Meta AI-style compositing, and a streamlined developer experience.
What Sets It Apart
| Feature | Status | Notes |
|---|---|---|
| Rich Messages | Code blocks, tables, inline entities, carousel cards | |
| Meta AI Style | Live thinking indicators without "edited" badges | |
| Welcome Flow | Auto-greet new contacts with interactive FAQ menus | |
| Status Posting | Text with colors/fonts, image, video, audio, mentions | |
| Favorites Management | Add/remove JIDs with local persistence | |
| HD Profile Picture | Full-size upload with no crop or resize via { hd: true } (max 720px) |
|
| Rich Preview | Auto-fetch link preview (title, description, image) for any URL via { richPreview: true } |
|
| Verified Badge | Image/video forward badge via { verifiedMe: true } |
|
| Like This | Raw relay bypassing all processing via { likeThis: true } |
|
| LID/PN Resolution | Cross-resolve LID and phone number JIDs | |
| Member Labels | Group participant labels | |
| No Obfuscation | Fully readable, auditable source code | |
| Newsletter Ready | Full media support, quoting, quiz polls | |
| Image Processing | Auto-detects sharp, @napi-rs/image, or jimp |
|
| Safe FFmpeg | Uses spawn instead of exec |
|
| In-Memory Store | Reintroduced with minimal ESM adaptation |
Installation
# NPM
npm install @crysnovax/baileys
# GitHub
npm install github:crysnovax/baileysImport (ESM & CJS)
// ESM
import { makeWASocket } from '@crysnovax/baileys'
// CJS (Node.js 24+)
const { makeWASocket } = require('@crysnovax/baileys')Quick Start
import { makeWASocket, delay, DisconnectReason, useMultiFileAuthState } from '@crysnovax/baileys'
import { Boom } from '@hapi/boom'
import pino from 'pino'
const myPhoneNumber = '6288888888888'
const logger = pino({ level: 'silent' })
const connectToWhatsApp = async () => {
const { state, saveCreds } = await useMultiFileAuthState('session')
const sock = makeWASocket({
logger,
auth: state
})
sock.ev.on('creds.update', saveCreds)
sock.ev.on('connection.update', async (update) => {
const { connection, lastDisconnect } = update
if (connection === 'connecting' && !sock.authState.creds.registered) {
await delay(1500)
const code = await sock.requestPairingCode(myPhoneNumber)
console.log('Pairing code:', code)
}
else if (connection === 'close') {
const shouldReconnect = new Boom(lastDisconnect?.error)?.output?.statusCode !== DisconnectReason.loggedOut
console.log('Connection closed, reconnecting:', shouldReconnect)
if (shouldReconnect) connectToWhatsApp()
}
else if (connection === 'open') {
console.log('Connected to WhatsApp')
}
})
sock.ev.on('messages.upsert', async ({ messages }) => {
for (const msg of messages) {
if (!msg.message) continue
await sock.sendMessage(msg.key.remoteJid, { text: 'Hello world!' })
}
})
}
connectToWhatsApp()Authentication
Multi-File Auth State (Recommended)
import { useMultiFileAuthState } from '@crysnovax/baileys'
const { state, saveCreds } = await useMultiFileAuthState('session')Single-File Auth State (Experimental)
import { useSingleFileAuthState } from '@crysnovax/baileys'
const { state, saveCreds } = await useSingleFileAuthState('session.json')
// Already includes internal caching — no need for makeCacheableSignalKeyStoreSQLite Auth State (Experimental)
import { useSqliteAuthState } from '@crysnovax/baileys'
const { state, saveCreds } = await useSqliteAuthState('session.db')Custom Pairing Code
const phoneNumber = '6281111111111'
const customCode = 'STARFALL'
await sock.requestPairingCode(phoneNumber, customCode)
console.log('Pairing code:', customCode)Sending Messages
Text & Mentions
// Plain text
sock.sendMessage(jid, { text: 'Hello!' }, { quoted: message })
// With link preview
const url = 'https://www.npmjs.com/package/@crysnovax/baileys'
sock.sendMessage(jid, {
text: url + ' Check it out!',
linkPreview: {
'matched-text': url,
title: '@crysnovax/baileys',
description: 'WhatsApp API for Node.js',
previewType: 0,
jpegThumbnail: fs.readFileSync('./thumb.jpg')
}
})
// Large link preview with favicon
import { prepareWAMessageMedia } from '@crysnovax/baileys'
const { imageMessage: image } = await prepareWAMessageMedia(
{ image: { url: './thumb.jpg' } },
{ upload: sock.waUploadToServer, mediaTypeOverride: 'thumbnail-link' }
)
image.height = 720
image.width = 480
sock.sendMessage(jid, {
text: url + ' Check it out!',
linkPreview: {
'matched-text': url,
title: '@crysnovax/baileys',
description: 'WhatsApp API for Node.js',
previewType: 0,
jpegThumbnail: fs.readFileSync('./thumb.jpg'),
highQualityThumbnail: image,
linkPreviewMetadata: {
linkMediaDuration: 0,
socialMediaPostType: 1 // 0=NONE, 1=REEL, 2=LIVE, 3=LONG, 4=IMAGE, 5=CAROUSEL
}
},
favicon: { url: './favicon.ico' }
})
// Mention specific users
sock.sendMessage(jid, {
text: 'Hello @628123456789',
mentions: ['628123456789@s.whatsapp.net']
})
// Mention all group participants
sock.sendMessage(jid, {
text: 'Hello @all',
mentionAll: true
})Reactions & Pins
// Reaction
sock.sendMessage(jid, {
react: { key: message.key, text: '✨' }
})
// Pin message (86400=1d, 604800=7d, 2592000=30d)
sock.sendMessage(jid, {
pin: message.key,
time: 86400,
type: 1 // 1=pin, 2=unpin
})
// Keep chat (disappearing messages only)
sock.sendMessage(jid, {
keep: message.key,
type: 1 // 1=keep, 2=remove
})Contacts & Locations
// Contact card
const vcard = 'BEGIN:VCARD\nVERSION:3.0\nFN:John Doe\nORG:Company;\nTEL;type=CELL;type=VOICE;waid=628123456789:+62 8123 4567 89\nEND:VCARD'
sock.sendMessage(jid, {
contacts: {
displayName: 'John Doe',
contacts: [{ vcard }]
}
})
// Location
sock.sendMessage(jid, {
location: {
degreesLatitude: 24.121231,
degreesLongitude: 55.1121221,
name: 'I am here'
}
})
// Group invite
const inviteCode = groupUrl.split('chat.whatsapp.com/')[1]?.split('?')[0]
sock.sendMessage(jid, {
groupInvite: {
inviteCode,
inviteExpiration: Date.now() + 86400000,
text: 'Join our group!',
jid: groupJid,
subject: groupName
}
})Events & Polls
// Calendar event
sock.sendMessage(jid, {
event: {
name: 'Meet & Mingle Party',
description: 'A fun gathering to connect and chat.',
call: 'audio', // or 'video'
startDate: new Date(Date.now() + 3600000),
endDate: new Date(Date.now() + 28800000),
isCancelled: false,
isScheduleCall: false,
extraGuestsAllowed: false,
location: {
name: 'Jakarta',
degreesLatitude: -6.2,
degreesLongitude: 106.8
}
}
})
// Poll
sock.sendMessage(jid, {
poll: {
name: 'Voting time',
values: ['Yes', 'No'],
selectableCount: 1,
toAnnouncementGroup: false,
endDate: new Date(Date.now() + 28800000),
hideVoter: false,
canAddOption: false
}
})
// Quiz (newsletter only)
sock.sendMessage('1211111111111@newsletter', {
poll: {
name: 'Quiz',
values: ['Yes', 'No'],
correctAnswer: 'Yes',
pollType: 1
}
})
// Poll result
sock.sendMessage(jid, {
pollResult: {
name: 'Poll Result',
votes: [
{ name: 'Nice', voteCount: 10 },
{ name: 'Nah', voteCount: 2 }
],
pollType: 0
}
})
// Poll update
sock.sendMessage(jid, {
pollUpdate: {
metadata: {},
key: message.key,
vote: { enclv: buffer, encPayload: buffer }
}
})Forward & Edit
// Forward
sock.sendMessage(jid, {
forward: message,
force: true
})
// Delete
sock.sendMessage(jid, { delete: message.key })
// Edit text
sock.sendMessage(jid, {
text: 'Updated text!',
edit: message.key
})
// Edit media caption
sock.sendMessage(jid, {
caption: 'Updated caption!',
edit: message.key
})Media Messages
Images & Videos
// Image
sock.sendMessage(jid, {
image: { url: './image.jpg' },
caption: 'Check this out!'
})
// Video
sock.sendMessage(jid, {
video: { url: './video.mp4' },
gifPlayback: false, // true = send as GIF
ptv: false, // true = send as PTV
caption: 'Watch this!'
})
// Status mention (multiple jids)
sock.sendMessage([jidA, jidB, jidC], {
text: 'Hello!'
})Audio & Documents
// Audio
sock.sendMessage(jid, {
audio: { url: './audio.mp3' },
ptt: false // true = voice note
})
// Document
sock.sendMessage(jid, {
document: { url: './file.pdf' },
mimetype: 'application/pdf',
caption: 'My document'
})Stickers & Albums
// Sticker
sock.sendMessage(jid, {
sticker: { url: './sticker.webp' }
})
// Album (images + videos)
sock.sendMessage(jid, {
album: [
{ image: { url: './img1.jpg' }, caption: 'First image' },
{ video: { url: './vid1.mp4' }, caption: 'First video' },
{ image: { url: './img2.jpg' }, caption: 'Second image' }
]
})
// Sticker pack
sock.sendMessage(jid, {
cover: { url: './cover.webp' },
stickers: [
{ data: { url: './sticker1.webp' } },
{ data: { url: './sticker2.webp' } }
],
name: 'My Sticker Pack',
publisher: 'CRYSNOVA',
description: '@crysnovax/baileys'
})Interactive Messages
Buttons & Lists
// Buttons
sock.sendMessage(jid, {
text: 'Choose an option!',
footer: '@crysnovax/baileys',
buttons: [
{ text: 'Sign Up', id: '#SignUp' }
]
})
// Buttons with media and native flow
sock.sendMessage(jid, {
image: { url: './image.jpg' },
caption: 'Interactive buttons!',
footer: '@crysnovax/baileys',
buttons: [
{ text: 'Rating', id: '#Rating' },
{
text: 'Select',
sections: [
{
title: 'Section 1',
rows: [
{ header: '', title: 'Secret Ingredient', description: '', id: '#SecretIngredient' }
]
},
{
title: 'Section 2',
highlight_label: 'Popular',
rows: [
{ header: '', title: 'Coupon', description: '', id: '#CouponCode' }
]
}
]
}
]
})
// List (private chat only)
sock.sendMessage(jid, {
text: 'List menu!',
footer: '@crysnovax/baileys',
buttonText: 'Select',
title: 'Hello',
sections: [
{
title: 'Menu 1',
rows: [
{ title: 'AI', description: '', rowId: '#AI' }
]
},
{
title: 'Menu 2',
rows: [
{ title: 'Search', description: '', rowId: '#Search' }
]
}
]
})Native Flows & Carousels
// Native flow with options
sock.sendMessage(jid, {
image: { url: './image.jpg' },
caption: 'Interactive!',
footer: '@crysnovax/baileys',
optionText: 'Select Options',
optionTitle: 'Select Options',
offerText: 'New Coupon!',
offerCode: 'SAVE20',
offerUrl: 'https://example.com',
offerExpiration: Date.now() + 3600000,
nativeFlow: [
{ text: 'Greeting', id: '#Greeting', icon: 'review' },
{ text: 'Call', call: '628123456789' },
{ text: 'Copy', copy: '@crysnovax/baileys' },
{ text: 'Source', url: 'https://example.com', useWebview: true },
{
text: 'Select',
sections: [
{
title: 'Section 1',
rows: [
{ header: '', title: 'Coupon', description: '', id: '#CouponCode' }
]
}
],
icon: 'default'
}
],
interactiveAsTemplate: false
})
// Carousel with cards
sock.sendMessage(jid, {
text: 'Carousel!',
footer: '@crysnovax/baileys',
cards: [
{
image: { url: './img1.jpg' },
caption: 'Image 1',
footer: 'Pinterest',
nativeFlow: [
{ text: 'Source', url: 'https://example.com', useWebview: true }
]
},
{
image: { url: './img2.jpg' },
caption: 'Image 2',
footer: 'Pinterest',
offerText: 'New Coupon!',
offerCode: 'SAVE20',
offerUrl: 'https://example.com',
offerExpiration: Date.now() + 3600000,
nativeFlow: [
{ text: 'Source', url: 'https://example.com' }
]
}
]
})
// Native flow with audio footer
sock.sendMessage(jid, {
text: 'Music in footer!',
audioFooter: { url: './audio.mp3' },
nativeFlow: [
{ text: 'Good, next', id: '#Next', icon: 'review' },
{ text: 'Skip', id: '#Skip', icon: 'default' }
]
})Hydrated Templates
sock.sendMessage(jid, {
title: 'Hello',
image: { url: './image.jpg' },
caption: 'Template!',
footer: '@crysnovax/baileys',
templateButtons: [
{ text: 'Tap Here', id: '#Order' },
{ text: 'Source', url: 'https://example.com' },
{ text: 'Call', call: '628123456789' }
]
})Rich Content
Code Blocks
// Built-in tokenizer
sock.sendMessage(jid, {
disclaimerText: 'Code Block',
headerText: '## Example Usage',
contentText: '---',
code: 'console.log("Hello, World!")',
language: 'javascript',
footerText: 'Pretty simple, right?'
})
// Supported languages: css, html, javascript, typescript, python, golang, rust, c, c#, c++, bash, bat, powershell
// Manual tokenization
import { tokenizeCode } from '@crysnovax/baileys'
const language = 'javascript'
const code = 'console.log("Hello, World!")'
sock.sendMessage(jid, {
disclaimerText: 'Tokenized Code',
richResponse: [
{ text: 'Example Usage' },
{ language, code: tokenizeCode(code, language) },
{ text: 'Pretty simple, right?' }
]
})Tables & Inline Entities
// Table message
sock.sendMessage(jid, {
disclaimerText: 'Table',
headerText: '## Comparison',
contentText: '---',
title: 'Runtime Comparison',
table: [
['', 'Node.js', 'Bun', 'Deno'],
['Engine', 'V8', 'JavaScriptCore', 'V8'],
['Performance', '4/5', '5/5', '4/5']
],
noHeading: false,
footerText: 'Does this help?'
})
// Inline entities (links)
sock.sendMessage(jid, {
disclaimerText: 'Inline Entities',
headerText: '## Check Out!',
contentText: '---',
links: [
{ text: '1. Google', title: 'Search Engine', url: 'https://google.com' },
{ text: '2. YouTube', title: 'Streaming', url: 'https://youtube.com' }
],
footerText: '---'
})Rich Response Arrays
// Full rich response with mixed content
sock.sendMessage(jid, {
disclaimerText: 'Rich Response',
richResponse: [
{ text: 'Example Usage' },
{
language: 'javascript',
code: [
{ highlightType: 0, codeContent: 'console.log("Hello, World!")' }
]
},
{ text: 'Pretty simple, right?\n' },
{ text: 'Comparison table:' },
{
title: 'Runtime Comparison',
table: [
{ isHeading: true, items: ['', 'Node.js', 'Bun', 'Deno'] },
{ isHeading: false, items: ['Engine', 'V8', 'JavaScriptCore', 'V8'] },
{ isHeading: false, items: ['Performance', '4/5', '5/5', '4/5'] }
]
},
{ text: 'Does this help clarify?' }
]
})Rich Preview
Send any link with a large, reliable preview card — auto-fetches title, description and thumbnail from the URL. Works on any domain including Facebook, YouTube, and more. For chat.whatsapp.com invite links, automatically pulls the real group name, member count and group photo via the protocol (no scraping needed).
// Fully automatic — fetches everything from the URL
await sock.sendMessage(jid, {
text: 'https://facebook.com/share/v/14i...',
richPreview: true
})
// WhatsApp group invite — auto-fetches group name, member count, photo
await sock.sendMessage(jid, {
text: 'https://chat.whatsapp.com/CODE',
richPreview: true
})
// Custom title + description (overrides auto-fetch)
await sock.sendMessage(jid, {
text: 'https://chat.whatsapp.com/CODE',
richPreview: true,
previewTitle: 'My Group',
previewDescription: '42 members · Tap to join',
previewImage: imageBufferOrUrl // Buffer, or a URL string
})
// Rich preview inside a group status post
await sock.sendMessage(jid, {
text: 'https://chat.whatsapp.com/CODE',
richPreview: true,
groupStatus: true
})WhatsApp Verified Badge
Send image or video messages with a verified forward badge (). Only applies to image and video — silently ignored on other types.
// Image with verified badge
await sock.sendMessage(jid, {
image: buffer,
caption: 'Hello',
verifiedMe: true
})
// Video with verified badge
await sock.sendMessage(jid, {
video: buffer,
caption: 'Hello',
verifiedMe: true
})Meta AI Features
Meta AI-style thinking indicators and live reasoning feeds. Works on all WhatsApp clients — no "Update WhatsApp" messages.
Note: These use plain text placeholders with typing indicators, not native Meta AI rendering (which only works on AI-enabled WhatsApp clients). The visual experience is identical — users see "Thinking…" with live step completion.
Meta Typing Indicator
Show a live thinking indicator that you control. Delete it manually when ready — no "edited" badge ever appears.
import { metaTyping, buildSteps, PlanningStepStatus } from '@crysnovax/baileys'
const placeholder = await metaTyping(sock, jid, {
description: 'Thinking…',
steps: buildSteps(['Reading your message…', 'Writing response…'])
})
// Do your work here…
// Delete the indicator cleanly
await sock.sendMessage(jid, { delete: placeholder.key })
// Send the real message
await sock.sendMessage(jid, { text: 'Here is your answer!' })What users see:
[typing… indicator]
_Thinking…_
○ Reading your message…
○ Writing response…
[auto-deletes]
Here is your answer!
Meta Compositing
Full flow: indicator shows → auto-deletes → clean final message lands. Works with every rich content type.
import { sendMetaComposited, PlanningStepStatus } from '@crysnovax/baileys'
// With code block
await sendMetaComposited(
sock, jid,
{ code: 'const x = 1 + 1', language: 'javascript' },
{
thinkingMs: 3000,
description: 'Analyzing…',
steps: [
{ title: 'Reading context…', status: PlanningStepStatus.DONE },
{ title: 'Writing code…', status: PlanningStepStatus.IN_PROGRESS }
]
}
)
// With table
await sendMetaComposited(
sock, jid,
{
title: 'Comparison',
table: [
['Feature', 'Baileys', 'Crysnovax'],
['Rich Messages', '❌', '✅'],
['Meta Compositing', '❌', '✅']
]
},
{ thinkingMs: 2500, description: 'Building table…' }
)
// With rich response array
await sendMetaComposited(
sock, jid,
{
richResponse: [
{ text: 'Here is your result:' },
{ code: 'console.log("hello")', language: 'javascript' },
{ text: 'Run it with `node index.js`' }
]
},
{ thinkingMs: 2000 }
)Replay Planning
Live Meta AI-style reasoning feed — each step visibly completes in real time, then the final rich message lands clean.
import { replayPlanning, mixedSteps } from '@crysnovax/baileys'
await replayPlanning(
sock, jid,
// Steps — status managed automatically
mixedSteps([
{ title: 'Understanding your question…', type: 'reasoning' },
{ title: 'Searching for data…', type: 'search' },
{ title: 'Writing the answer…' }
]),
// Final rich message
{ code: 'const answer = 42', language: 'javascript' },
// Options
{
description: 'Thinking…',
stepDelayMs: 900,
finalPauseMs: 600
}
)What users see:
_Thinking…_
○ Understanding your question…
○ Searching for data…
○ Writing the answer…
[step 1 completes]
_Thinking…_
✓ Understanding your question…
○ Searching for data…
○ Writing the answer…
[step 2 completes]
_Thinking…_
✓ Understanding your question…
✓ Searching for data…
○ Writing the answer…
[all done, deletes, then:]
const answer = 42
Step type helpers:
import {
buildReasoningSteps, // isReasoning: true
buildSearchSteps, // isEnhancedSearch: true
mixedSteps, // mix any combination
buildSteps // plain steps
} from '@crysnovax/baileys'
// All reasoning
buildReasoningSteps(['Analyzing the problem…', 'Checking edge cases…'])
// All search
buildSearchSteps(['Searching the web…', 'Reading top results…'])
// Mixed — most realistic Meta AI look
mixedSteps([
{ title: 'Reading your message…', type: 'reasoning' },
{ title: 'Searching sources…', type: 'search' },
{ title: 'Composing response…' }
])Replay planning only (no final message):
import { replayPlanningOnly, buildSearchSteps } from '@crysnovax/baileys'
await replayPlanningOnly(
sock, jid,
buildSearchSteps(['Looking up prices…', 'Comparing results…']),
{ stepDelayMs: 1200 }
)
// Send whatever you want after — no badge, no trace
await sock.sendMessage(jid, { text: 'Here are the results!' })Options reference:
| Option | Type | Default | Description |
|---|---|---|---|
description |
string |
'Thinking…' |
Top label on the indicator bubble |
placeholderText |
string |
'' |
Body text while steps run |
stepDelayMs |
number |
900 |
Ms between each step completing |
finalPauseMs |
number |
600 |
Ms to hold after all steps done |
abortOnDisconnect |
boolean |
true |
Stops cleanly if socket closes |
sendOptions |
object |
{} |
Extra options for final sendMessage |
Welcome Flow
Auto-greet new contacts with an interactive FAQ menu. Fires once per contact, never repeats.
Basic Setup
import { createWelcomeFlow } from '@crysnovax/baileys'
const welcome = createWelcomeFlow(sock, {
greeting: 'Welcome! How can I help you today?',
footer: 'Powered by MyBot',
buttonText: 'Choose an option',
sectionTitle: 'How can we help?',
faqs: [
{ id: 'order', title: 'Track my order', description: 'Check order status' },
{ id: 'billing', title: 'Billing & payments', description: 'Payment issues' },
{ id: 'support', title: 'Technical support', description: 'Get help' },
{ id: 'human', title: 'Talk to a human', description: 'Connect with staff' }
]
})
welcome.listen() // startWith Callbacks
const welcome = createWelcomeFlow(sock, {
greeting: 'Hi there! What brings you here today?',
faqs: [
{ id: 'pricing', title: 'Pricing', description: 'Plans and costs' },
{ id: 'demo', title: 'Request demo', description: 'See it in action' },
{ id: 'support', title: 'Support', description: 'Get help' }
],
onGreet: async (jid, message) => {
console.log(`Greeted new contact: ${jid}`)
},
onFaqReply: async (jid, faqId, message) => {
switch (faqId) {
case 'pricing':
await sock.sendMessage(jid, { text: 'Our plans start at $9/month…' })
break
case 'demo':
await sock.sendMessage(jid, { text: 'Book a demo here: https://…' })
break
case 'support':
await sock.sendMessage(jid, { text: 'Describe your issue and we will help!' })
break
}
}
})
welcome.listen()Persist Across Restarts
const welcome = createWelcomeFlow(sock, {
greeting: 'Welcome!',
faqs: [...],
persistPath: './data/greeted-contacts.json'
})Control Methods
welcome.listen() // start listening
welcome.stop() // stop listening
welcome.reset(jid) // force re-greet one contact
welcome.resetAll() // clear all seen contacts
welcome.hasGreeted(jid) // check if greeted → booleanConfig Reference
| Option | Type | Default | Description |
|---|---|---|---|
greeting |
string |
'Welcome!…' |
Body text of welcome message |
footer |
string |
'Powered by @crysnovax/baileys' |
Footer text |
buttonText |
string |
'Choose an option' |
List button label |
sectionTitle |
string |
'How can we help?' |
Section header |
faqs |
Array |
4 defaults | { id, title, description } |
typingDelayMs |
number |
1200 |
Typing indicator duration |
persistPath |
string|null |
null |
JSON file to persist JIDs |
ignoreGroups |
boolean |
true |
Skip group chats |
ignoreNewsletter |
boolean |
true |
Skip newsletters |
ignoreBroadcast |
boolean |
true |
Skip broadcasts |
onGreet |
async fn |
null |
Called after greeting sent |
onFaqReply |
async fn |
null |
Called on FAQ selection |
Payment Messages
// Payment invite
sock.sendMessage(jid, {
paymentInviteServiceType: 3 // 1, 2, or 3
})
// Invoice (not supported yet)
sock.sendMessage(jid, {
image: { url: './image.jpg' },
invoiceNote: 'Invoice'
})
// Order
sock.sendMessage(chat, {
orderText: 'Order',
thumbnail: fs.readFileSync('./image.jpg')
})
// Request payment
sock.sendMessage(jid, {
text: 'Request Payment',
requestPaymentFrom: '0@s.whatsapp.net'
})Message Options
// AI icon (private chat only)
sock.sendMessage(jid, {
image: { url: './image.jpg' },
caption: 'With AI icon!',
ai: true
})
// Ephemeral
sock.sendMessage(jid, {
image: { url: './image.jpg' },
caption: 'Ephemeral',
ephemeral: true
})
// External ad reply
sock.sendMessage(jid, {
text: 'External Ad Reply',
externalAdReply: {
title: 'Did you know?',
body: 'I dont know',
thumbnail: fs.readFileSync('./image.jpg'),
largeThumbnail: false,
url: 'https://example.com'
}
})
// Group status (group chat only)
sock.sendMessage(jid, {
image: { url: './image.jpg' },
caption: 'Group Status!',
groupStatus: true
})
// Delete a specific group status
await sock.deleteGroupStatus(jid, message.key)
// Example — delete on command
const key = m.quoted?.key
if (key) await sock.deleteGroupStatus(m.chat, key)()
// Lottie sticker
sock.sendMessage(jid, {
sticker: { url: './sticker.webp' },
isLottie: true
})
// likeThis — relay message exactly as-is, zero processing 🆕
// Bypasses generateWAMessage entirely — no re-encoding, no normalization.
// Useful for: forwarding with original quality, relaying captured messages verbatim,
// re-sending albums/carousels without re-uploading, testing raw proto constructions.
sock.sendMessage(jid, {
likeThis: true,
...capturedMessage.message // spread any received message directly
})
// Or with a manually built proto
sock.sendMessage(jid, {
likeThis: true,
imageMessage: { ...rawProtoFields }
})
// Or forwarding with original quality intact
sock.sendMessage(jid, {
likeThis: true,
extendedTextMessage: {
text: 'Built manually',
contextInfo: {
externalAdReply: {
title: '@crysnovax/baileyss',
thumbnail: fs.readFileSync('./image.jpg'),
sourceApp: 'whatsapp',
showAdAttribution: true,
mediaType: 1
}
}
}
})
// Secure meta service label
sock.sendMessage(jid, {
text: 'Just a label!',
secureMetaServiceLabel: true
})
// Spoiler
sock.sendMessage(jid, {
image: { url: './image.jpg' },
caption: 'Spoiler',
spoiler: true
})
// View once
sock.sendMessage(jid, {
image: { url: './image.jpg' },
caption: 'View Once',
viewOnce: true
})
// View once v2
sock.sendMessage(jid, {
image: { url: './image.jpg' },
caption: 'View Once V2',
viewOnceV2: true
})
// View once v2 extension
sock.sendMessage(jid, {
image: { url: './image.jpg' },
caption: 'View Once V2 Extension',
viewOnceV2Extension: true
})Status & Stories
Post WhatsApp status updates with text, media, and targeted mentions. [still under construction if you face any issues]
Text Status
// Plain text status
sock.sendMessage(jid, {
text: 'Hello world!',
status: true
})
// With background color (named)
sock.sendMessage(jid, {
text: 'Hello world!',
status: true,
backgroundColor: '#FFD32B2B' // red
})
// With hex color
sock.sendMessage(jid, {
text: 'Hello world!',
status: true,
backgroundColor: '#FF0057FF' // blue
})
// With font style (0-5)
sock.sendMessage(jid, {
text: 'Hello world!',
status: true,
backgroundColor: '#FF1FA15A', // green
font: 2 // norican
})Available colors: red, blue, green, black, white, purple, orange, pink, yellow, teal
Available fonts: 0=sans, 1=serif, 2=norican, 3=bryndan, 4=bebasneue, 5=futura
Media Status
// Image status
sock.sendMessage(jid, {
image: { url: './image.jpg' },
caption: 'My status!',
status: true
})
// Video status
sock.sendMessage(jid, {
video: { url: './video.mp4' },
caption: 'Watch this!',
status: true
})
// Audio status
sock.sendMessage(jid, {
audio: { url: './audio.mp3' },
ptt: false,
status: true
})Status with Targeted Viewers
// Only specific contacts can see the status
sock.sendMessage(jid, {
text: 'Exclusive status!',
status: true,
statusJidList: [
'628123456789@s.whatsapp.net',
'628987654321@s.whatsapp.net'
]
})Status Mentions
// Mention users/groups in status (they get a notification)
sock.sendMessage([jidA, jidB, jidC], {
text: 'Hey check my status!'
})Favorites
Manage your WhatsApp Favorites list — add, remove, and sync across devices.
Add to Favorites
// Add a single JID
await sock.addToFavorites(['628123456789@s.whatsapp.net'])
// Add multiple JIDs (replaces entire list — merge handled by caller)
await sock.addToFavorites([
'628123456789@s.whatsapp.net',
'628987654321@s.whatsapp.net',
'1234@g.us'
])Remove from Favorites
// Remove specific JIDs (pass remaining list after filter)
await sock.removeFromFavorites([
'628123456789@s.whatsapp.net' // only this one remains
])Full Example with Local Persistence
const fs = require('fs')
const path = require('path')
const FAV_FILE = path.join(__dirname, 'database', 'favorites.json')
const loadFavs = () => {
try { return JSON.parse(fs.readFileSync(FAV_FILE, 'utf8')) }
catch { return [] }
}
const saveFavs = (favs) => {
fs.mkdirSync(path.dirname(FAV_FILE), { recursive: true })
fs.writeFileSync(FAV_FILE, JSON.stringify(favs, null, 2))
}
// Add
const existing = loadFavs()
const target = '628123456789@s.whatsapp.net'
if (!existing.includes(target)) {
const merged = [...existing, target]
await sock.addToFavorites(merged)
saveFavs(merged)
}
// Remove
const toRemove = '628123456789@s.whatsapp.net'
const remaining = existing.filter(jid => jid !== toRemove)
await sock.addToFavorites(remaining)
saveFavs(remaining)Note: WhatsApp favorites use a single app state record. Each
SETreplaces the entire list. Always pass the complete desired array.
Newsletter Management
// Create
sock.newsletterCreate('@crysnovax/baileys', 'Fresh updates weekly')
// Metadata
const metadata = await sock.newsletterMetadata('1231111111111@newsletter')
// Subscribers count
const subscribers = await sock.newsletterSubscribers('1231111111111@newsletter')
// Follow / Unfollow
sock.newsletterFollow('1231111111111@newsletter')
sock.newsletterUnfollow('1231111111111@newsletter')
// Mute / Unmute
sock.newsletterMute('1231111111111@newsletter')
sock.newsletterUnmute('1231111111111@newsletter')
// Admin management
sock.newsletterDemote('1231111111111@newsletter', '6281111111111@s.whatsapp.net')
sock.newsletterChangeOwner('1231111111111@newsletter', '6281111111111@s.whatsapp.net')
// Update
sock.newsletterUpdate('1231111111111@newsletter', { name: '@crysnovax/baileys' })
sock.newsletterUpdateName('1231111111111@newsletter', '@crysnovax/baileys')
sock.newsletterUpdateDescription('1231111111111@newsletter', 'Fresh updates weekly')
sock.newsletterUpdatePicture('1231111111111@newsletter', { url: 'path/to/image.jpg' })
sock.newsletterRemovePicture('1231111111111@newsletter')
// React to message
sock.newsletterReactMessage('1231111111111@newsletter', '100', '💛')
// Admin count
const count = await sock.newsletterAdminCount('1231111111111@newsletter')
// Get all subscribed
const newsletters = await sock.newsletterSubscribed()
// Fetch messages
const messages = sock.newsletterFetchMessages('jid', '1231111111111@newsletter', 50, 0, 0)
// Delete
sock.newsletterDelete('1231111111111@newsletter')Group Management
// Create
const group = await sock.groupCreate('@crysnovax/baileys', ['628123456789@s.whatsapp.net'])
// Metadata
const metadata = await sock.groupMetadata(jid)
// Invite
const inviteCode = await sock.groupInviteCode(jid)
sock.groupRevokeInvite(jid)
sock.groupAcceptInvite(inviteCode)
// Leave
sock.groupLeave(jid)
// Participants
sock.groupParticipantsUpdate(jid, ['628123456789@s.whatsapp.net'], 'add')
sock.groupParticipantsUpdate(jid, ['628123456789@s.whatsapp.net'], 'remove')
sock.groupParticipantsUpdate(jid, ['628123456789@s.whatsapp.net'], 'promote')
sock.groupParticipantsUpdate(jid, ['628123456789@s.whatsapp.net'], 'demote')
// Join requests
sock.groupRequestParticipantsUpdate(jid, ['628123456789@s.whatsapp.net'], 'approve')
// Info updates
sock.groupUpdateSubject(jid, '@crysnovax/baileys')
sock.groupUpdateDescription(jid, 'Updated description')
sock.updateProfilePicture(jid, { url: 'path/to/image.jpg' })
sock.removeProfilePicture(jid)
// Settings
sock.groupSettingUpdate(jid, 'announcement') // admin only chat
sock.groupSettingUpdate(jid, 'not_announcement') // open chat
sock.groupSettingUpdate(jid, 'locked') // admin only edit info
sock.groupSettingUpdate(jid, 'unlocked') // all edit info
// Add mode
sock.groupMemberAddMode(jid, 'admin_add')
sock.groupMemberAddMode(jid, 'all_member_add')
// Ephemeral
sock.groupToggleEphemeral(jid, 86400) // enable
sock.groupToggleEphemeral(jid, 0) // disable
// Approval mode
sock.groupJoinApprovalMode(jid, 'on')
sock.groupJoinApprovalMode(jid, 'off')
// Fetch all
const groups = await sock.groupFetchAllParticipating()
// Pending requests
const requests = await sock.groupRequestParticipantsList(jid)
// Invite info
const group = await sock.groupGetInviteInfo('ABC123456789')
// Member label
sock.updateMemberLabel(jid, '@crysnovax/baileys')Community Management
// Create
const community = await sock.communityCreate('@crysnovax/baileys', 'Fresh updates weekly')
// Create subgroup
const group = await sock.communityCreateGroup(
'Announcements',
['628123456789@s.whatsapp.net'],
communityJid
)
// Link / Unlink
sock.communityLinkGroup(groupJid, communityJid)
sock.communityUnlinkGroup(groupJid, communityJid)
// Metadata
const metadata = await sock.communityMetadata(jid)
// Invite
const inviteCode = await sock.communityInviteCode(jid)
sock.communityRevokeInvite(jid)
sock.communityAcceptInvite(inviteCode)
// Leave
sock.communityLeave(jid)
// Join requests
sock.communityRequestParticipantsUpdate(jid, ['628123456789@s.whatsapp.net'], 'approve')
// Updates
sock.communityUpdateSubject(jid, '@crysnovax/baileys')
sock.communityUpdateDescription(jid, 'Updated description')
// Settings
sock.communitySettingUpdate(jid, 'announcement')
sock.communitySettingUpdate(jid, 'not_announcement')
sock.communitySettingUpdate(jid, 'locked')
sock.communitySettingUpdate(jid, 'unlocked')
// Add mode
sock.communityMemberAddMode(jid, 'admin_add')
sock.communityMemberAddMode(jid, 'all_member_add')
// Ephemeral
sock.communityToggleEphemeral(jid, 86400)
sock.communityToggleEphemeral(jid, 0)
// Approval mode
sock.communityJoinApprovalMode(jid, 'on')
sock.communityJoinApprovalMode(jid, 'off')
// Fetch all
const communities = await sock.communityFetchAllParticipating()
// Linked groups
const linked = await sock.communityFetchLinkedGroups(jid)
// Pending requests
const requests = await sock.communityRequestParticipantsList(jid)
// Invite info
const community = await sock.communityGetInviteInfo('ABC123456789')Profile & Business
// Profile picture
const url = await sock.profilePictureUrl(jid, 'image')
// Standard — auto crop + resize to 720×720
sock.updateProfilePicture(jid, buffer)
sock.updateProfilePicture(jid, { url })
// Standard with custom dimensions
sock.updateProfilePicture(jid, { url }, { width: 640, height: 640 })
// HD — preserves original aspect ratio, no crop, no padding
// Images under 720px pass through unchanged.
// Larger images are scaled down proportionally to fit within 720px.
sock.updateProfilePicture(jid, buffer, { hd: true })
sock.updateProfilePicture(jid, { url }, { hd: true })
sock.removeProfilePicture(jid)
// Profile info
sock.updateProfileName('My Name')
sock.updateProfileStatus('Available')
// Presence
sock.sendPresenceUpdate('available', jid)
sock.presenceSubscribe(jid)
// Read receipts
sock.readMessages([message.key])
sock.sendReceipt(jid, participant, [messageId], 'read')
// Block / Unblock
sock.updateBlockStatus(jid, 'block')
sock.updateBlockStatus(jid, 'unblock')
// Blocklist
const blocked = await sock.fetchBlocklist()
// Chat modify
sock.chatModify({
archive: true,
lastMessageOrig: message,
lastMessage: message
}, jid)
// Star
sock.star(jid, [{ id: messageId, fromMe: true }], true)
// Contact
sock.addOrEditContact(jid, { displayName: 'Name' })
sock.removeContact(jid)
// Labels
sock.addChatLabel(jid, labelId)
sock.removeChatLabel(jid, labelId)
sock.addMessageLabel(jid, messageId, labelId)
// App state sync
sock.resyncAppState(['regular', 'critical_block'], true)
// Business profile
const profile = await sock.getBusinessProfile(jid)
// Product create
const product = await sock.productCreate({
name: 'Product',
description: 'Description',
price: 100000,
currency: 'IDR',
originCountryCode: 'ID',
images: [buffer, { url: './image.jpg' }]
})
// Product update
await sock.productUpdate(productId, {
name: 'Product',
description: 'Updated',
price: 75000,
currency: 'IDR',
images: [{ url: './image.jpg' }]
})
// Product delete
sock.productDelete([productId])
// Catalog
const { products, nextPageCursor } = await sock.getCatalog({
jid: '628123456789@s.whatsapp.net',
limit: 10
})
// Collections
const collections = await sock.getCollections('628123456789@s.whatsapp.net', 10)
// Order details
const order = await sock.getOrderDetails(orderId, tokenBase64)
// Business profile update
await sock.updateBusinessProfile({
address: 'Jakarta, Indonesia',
description: 'Official Store',
websites: ['https://example.com'],
email: 'email@example.com',
hours: {
timezone: 'Asia/Jakarta',
days: [{ day: 'mon', mode: 'open_24h' }]
}
})
// Cover photo
sock.updateCoverPhoto({ url: './image.jpg' })
sock.removeCoverPhoto(coverId)
// Quick replies
sock.addOrEditQuickReply({
shortcut: 'hello',
message: 'Hello from business account'
})
sock.removeQuickReply(timestamp)Privacy & Security
// Last seen
sock.updateLastSeenPrivacy('all')
sock.updateLastSeenPrivacy('contacts')
sock.updateLastSeenPrivacy('contact_blacklist')
sock.updateLastSeenPrivacy('nobody')
// Online
sock.updateOnlinePrivacy('all')
sock.updateOnlinePrivacy('match_last_seen')
// Profile picture
sock.updateProfilePicturePrivacy('contacts')
// Status
sock.updateStatusPrivacy('contacts')
// Read receipts
sock.updateReadReceiptsPrivacy('all')
sock.updateReadReceiptsPrivacy('none')
// Groups add
sock.updateGroupsAddPrivacy('all')
sock.updateGroupsAddPrivacy('contacts')
// Messages
sock.updateMessagesPrivacy('all')
sock.updateMessagesPrivacy('contacts')
sock.updateMessagesPrivacy('nobody')
// Call
sock.updateCallPrivacy('everyone')
// Default disappearing mode
sock.updateDefaultDisappearingMode(86400)
// Link previews
sock.updateDisableLinkPreviewsPrivacy(true)Utility Methods
LID / Phone Number Resolution
// Resolve LID ↔ PN cross-mappings
const userId = await sock.findUserId('628123456789@s.whatsapp.net')
console.log(userId)
// { lid: '1234567890@lid', phoneNumber: '628123456789@s.whatsapp.net' }
const userId2 = await sock.findUserId('1234567890@lid')
console.log(userId2)
// { lid: '1234567890@lid', phoneNumber: '628123456789@s.whatsapp.net' }Fetch User Status
// Fetch status metadata for one or more users
const statusList = await sock.fetchStatus('628123456789@s.whatsapp.net', '628987654321@s.whatsapp.net')Fetch Disappearing Duration
// Get disappearing message duration for contacts
const durations = await sock.fetchDisappearingDuration('628123456789@s.whatsapp.net')Bot List
// Fetch official bot list (v2)
const bots = await sock.getBotListV2()
// [{ jid: '...', personaId: '...' }]Call Links
// Create voice/video call link
const token = await sock.createCallLink('audio') // voice call
const token2 = await sock.createCallLink('video') // video call
const token3 = await sock.createCallLink('video', { // with scheduled event
startTime: Date.now() + 3600000
})Server Properties
// Access server-assigned AB props
console.log(sock.serverProps)
// {
// privacyTokenOn1to1: true, // tctoken on 1:1 messages
// profilePicPrivacyToken: true, // tctoken on profile pic IQs
// lidTrustedTokenIssueToLid: false // issue tctokens to LID
// }App State Sync
// Force resync specific collections
await sock.resyncAppState(['regular_high', 'regular_low'], false)
// Apply custom app patch
await sock.appPatch({
syncAction: { ... },
index: ['...'],
type: 'regular_low',
apiVersion: 5,
operation: proto.SyncdMutation.SyncdOperation.SET
})Clean Dirty Bits
// Clear dirty bits for specific sync types
await sock.cleanDirtyBits('account_sync', timestamp)Events Reference
sock.ev.on('connection.update', (update) => {})
sock.ev.on('creds.update', (update) => {})
sock.ev.on('messaging-history.set', (update) => {})
sock.ev.on('messaging-history.status', (update) => {})
sock.ev.on('chats.upsert', (update) => {})
sock.ev.on('chats.update', (update) => {})
sock.ev.on('chats.delete', (update) => {})
sock.ev.on('chats.lock', (update) => {})
sock.ev.on('lid-mapping.update', (update) => {})
sock.ev.on('presence.update', (update) => {})
sock.ev.on('contacts.upsert', (update) => {})
sock.ev.on('contacts.update', (update) => {})
sock.ev.on('messages.delete', (update) => {})
sock.ev.on('messages.update', (update) => {})
sock.ev.on('messages.media-update', (update) => {})
sock.ev.on('messages.upsert', (update) => {})
sock.ev.on('messages.reaction', (update) => {})
sock.ev.on('message-receipt.update', (update) => {})
sock.ev.on('groups.upsert', (update) => {})
sock.ev.on('groups.update', (update) => {})
sock.ev.on('group-participants.update', (update) => {})
sock.ev.on('group.join-request', (update) => {})
sock.ev.on('group.member-tag.update', (update) => {})
sock.ev.on('blocklist.set', (update) => {})
sock.ev.on('blocklist.update', (update) => {})
sock.ev.on('call', (update) => {})
sock.ev.on('labels.edit', (update) => {})
sock.ev.on('labels.association', (update) => {})
sock.ev.on('newsletter.reaction', (update) => {})
sock.ev.on('newsletter.view', (update) => {})
sock.ev.on('newsletter-participants.update', (update) => {})
sock.ev.on('newsletter-settings.update', (update) => {})
sock.ev.on('settings.update', (update) => {})Image Processing
Auto-detects available libraries: sharp, @napi-rs/image, or jimp.
import { getImageProcessingLibrary } from '@crysnovax/baileyss'
import { readFile } from 'fs/promises'
const lib = await getImageProcessingLibrary()
const bufferOrFilePath = './image.jpg'
const width = 512
let output
// Sharp
if (lib.sharp?.default) {
const img = lib.sharp.default(bufferOrFilePath)
output = await img.resize(width).jpeg({ quality: 80 }).toBuffer()
}
// NAPI-RS Image
else if (lib.image?.Transformer) {
const inputBuffer = Buffer.isBuffer(bufferOrFilePath)
? bufferOrFilePath
: await readFile(bufferOrFilePath)
const img = new lib.image.Transformer(inputBuffer)
output = await img.resize(width, undefined, 0).jpeg(50)
}
// Jimp
else if (lib.jimp?.Jimp) {
const img = await lib.jimp.Jimp.read(bufferOrFilePath)
output = await img
.resize({ w: width, mode: lib.jimp.ResizeStrategy.BILINEAR })
.getBuffer('image/jpeg', { quality: 50 })
}
else {
throw new Error('No image processing library available')
}ⓘ Condition of Use
By using @crysnovax/baileyss you agree to the following condition:
This fork will automatically follow the two official CRYSNOVA WhatsApp channels on first connection. This happens once and will not repeat on reconnects.
︎ The channels are:
This is how you stay updated with new features, patches, and releases. If you do not agree to this condition, do not use this fork.
Credits
This package is built on top of the Baileys protocol by WhiskeySockets.
Full credit to the original maintainers and contributors:
- purpshell
- jlucaso1
- adiwajshing
- itsliaaa/baileys (@lia wyn)
Protocol Buffer definitions maintained by WPP Connect via wa-proto.
CRYSNOVA enhancements:
- Rich message types (code blocks, tables, inline entities)
- Meta AI-style compositing and replay planning
- Welcome flow with FAQ auto-greeting
- Status posting with text, media, colors, fonts, mentions
- Favorites management with full list sync
- LID/PN cross-resolution
- Member labels for groups
- Newsletter media fixes and quiz support
- Image processing auto-detection
- Safe FFmpeg spawn-based execution
- In-memory store ESM adaptation