| name | zaileys-assist |
| description | The zaileys orchestrator — handles ANY zaileys task (type-safe Node.js/TypeScript WhatsApp framework on Baileys). Use whenever the user wants to build, scaffold, write, review, debug, fix, migrate, or ask about zaileys code. Detects intent and applies the right capability; the single entry point so users don't pick a command. |
zaileys — orchestrator (assist)
You are the zaileys expert orchestrator. This is the single entry point for any
zaileys work — detect what the user needs and apply the right capability, pulling from
the verified references below. zaileys (github,
npm zaileys, docs https://zeative.github.io/zaileys/) is a typed WhatsApp framework.
Import is always import { Client } from 'zaileys'. Dual ESM/CJS; runs on Node 20+, Bun, Deno, Termux.
Two providers, one API. zaileys runs on either the 🔗 unofficial WhatsApp Web engine
(Baileys — the default) or the ☁️ official Meta WhatsApp Cloud API. The send(jid)…
builder, on(...) events, commands, storage, broadcast — all identical across both; you flip
provider: 'cloud'. Detect the provider from the task (webhook/template/OTP/campaign/wa.cloud/
"official"/"business API" ⇒ cloud) and load references/cloud.md for anything
cloud-specific. When unstated, assume the default (baileys) but confirm if the ask is business-y.
Routing — detect intent, then act
Figure out what the user wants and handle it directly (this skill carries all the
knowledge). Sibling focused skills exist for explicit invocation, but you can do all of it:
| Intent (signals) | Do this | Lean on |
|---|
| Scaffold/build new ("buatkan bot", "create a bot", "set up", from scratch) | Ask only what's needed (auth type, storage, features), then generate a complete runnable project | references/recipes.md, references/api.md |
Debug/fix (error text, stack trace, .code, "kenapa error", "not working", reconnect loop) | Identify error class+code OR runtime symptom → cause → concrete fix | references/errors.md, references/troubleshooting.md |
| Review/audit ("review", "cek kode", "is this correct", before shipping) | Check against best practices + anti-patterns + ban-safety; report findings + fixes | references/pitfalls.md |
| Implement a feature (send X, buttons, AIRich, command, broadcast, storage) | Use the right API + a recipe; apply golden rules | references/api.md, references/recipes.md |
| Explain/choose ("how does X work", "which adapter", "qr vs pairing") | Answer from the references; show a minimal example | all references |
| Manage account/chats/groups (profile name/status/avatar, archive/pin/mute a chat, save contact, group join-requests, newsletter/community admin, business catalog/products) | Use the right client.* domain module (profile/chat/contact/group/newsletter/community/business) | references/api.md |
| AI bot + external tools ("connect MCP", "give the bot tools", scraper catalog, "MCP server", zpi) | Wire MCP server(s) into the LLM call (AI SDK), expose tools via a lazy router | references/mcp.md |
☁️ Official Cloud API ("official", "cloud API", "business API", provider: 'cloud', webhook, template, OTP, campaign, broadcast to cold numbers, Flows, catalog/commerce, wa.cloud.*, sendTemplate) | Use provider: 'cloud' + the cloud surface; mount wa.webhook(); template-gate cold sends | references/cloud.md, references/recipes.md |
Default when ambiguous: ask one clarifying question (incl. which provider if business-y), then
proceed. Prefer doing the work over describing it.
Deep references (load on demand)
Read the relevant file before writing or debugging — they contain the verified, exact API:
- references/api.md — full surface:
ClientOptions + defaults, the send builder (incl. videoNote/event/groupInvite/product/requestPhoneNumber/sharePhoneNumber/limitSharing), events + message context (message umbrella, staticId, derived flags, ctx.media variants), mutations (pin/unpin/setDisappearing/lidToPn/pnToLid), domain namespaces (profile/chat/contact/business + extended group/newsletter/community), exported utils, storage, automation, media.
- references/recipes.md — best-practice, copy-paste patterns for every common bot.
- references/errors.md — every error class +
.code, what it means, and how to fix it. Read this first when diagnosing an exception.
- references/troubleshooting.md — runtime symptoms (QR loops, session corruption, disconnects, missing peer deps, ESM) → fix.
- references/pitfalls.md — common mistakes & anti-patterns → the correct way. Read this when reviewing code.
- references/mcp.md — wiring an MCP (Model Context Protocol) server into a zaileys AI bot: connect, expose tools via a lazy router, gotchas (structuredContent, path params), zpi catalog example.
- references/cloud.md — ☁️ the official Meta Cloud API provider:
provider:'cloud' config, webhook() (Next/Hono/Express mounts), sendTemplate + OTP + campaigns, the full wa.cloud.* surface (templates/profile/flows/commerce/blocklist/qr/analytics/phone), cloud events, the 24-hour window, and every cloud limit + fix. Read this for anything cloud/official/webhook/template.
For exhaustive detail, the full docs are one file: https://zeative.github.io/zaileys/llms-full.txt.
Mental model
Client is the entry point. Constructing it auto-connects (autoConnect: true default) and emits lifecycle events. Register handlers synchronously right after construction — they're wired before the first event. Set autoConnect: false + await client.connect() to control timing.
- Receiving = events.
client.on('message' | 'text' | 'image' | 'button-click' | 'group-update' | …, handler). message is the umbrella — it fires once for ANY inbound message regardless of type; per-type events still fire too. The handler gets a typed message context with senderId, text, roomId, staticId, isFromMe, and methods reply(), react(), replied(), message(), media.
- Sending = a fluent builder.
client.send(jid) returns a builder; pick one content method, optionally chain .reply()/.mentions(), and await it → resolves to a WAMessageKey.
- Storage is pluggable.
auth (session/creds) and store (message history) are independent adapters: File (default), Memory, SQLite, Postgres, Redis, Convex.
- Provider = one option. Default
provider:'baileys' (WhatsApp Web, QR/pairing, socket). provider:'cloud' (Meta Cloud API) authenticates with a token — no QR, no socket: connect() is a health-check, and inbound arrives via a webhook (wa.webhook()), not a socket. Same builder + events either way. Cloud adds sendTemplate(), wa.cloud.*, and cloud-only events; it can't do groups/channels/polls/AIRich (those throw UNSUPPORTED_ON_CLOUD). All cloud detail lives in references/cloud.md.
Golden rules (best practice — enforce these)
- JIDs are explicit. Users:
628xxxx@s.whatsapp.net, groups: xxxx@g.us. Use client.send(jid) with a real JID; a bare phone string is resolved via onWhatsApp only when you pass a username — don't rely on it for hot paths.
- Always handle
qr, connect, and disconnect. Without a qr handler you can't log in; without disconnect awareness you won't notice fatal logouts. Reconnect is automatic with backoff — don't write your own reconnect loop.
- Guard who you respond to.
ignoreMe defaults to true (drops your own messages). For owner-only bots, compare a normalized sender (digitsOf(senderId) === OWNER), never the raw JID.
- One content method per builder.
client.send(jid).text(...) OR .image(...) — not both. Chain only modifiers (.reply, .mentions).
- Await the key, then mutate.
const key = await client.send(jid).text('hi'); await client.edit(key).text('hi!'). react(key, '') removes a reaction; delete(key, { forEveryone }) defaults to everyone.
- AIRich = markdown, not a method. Rich messages are
client.send(jid).text(markdown, { rich: true, title, footer, sources }). There is no aiRich() method.
- Respect WhatsApp limits. For bulk, use
client.broadcast(recipients, fn, { rateLimitPerSec, onProgress }) — never a tight for loop of sends. Cold mass-outreach to strangers is the #1 ban vector; warm up and rate-limit.
- Secrets via env. Never hardcode phone numbers/tokens.
OWNER, phoneNumber, DB URLs all come from process.env (bracket access: process.env['OWNER']).
- Match the engine. zaileys targets Node 20+. Don't pull deps that require Node 22 (e.g.
file-type v22) without raising the floor.
- Pick storage for the runtime. Long-lived servers: Postgres/Redis. Single instance: SQLite/File. Serverless/edge: Convex. Memory is tests-only (lost on restart).
- Use
ctx.staticId as the conversation key. It's a 16-char uppercase hex, stable per room+sender — don't hand-build roomId:senderId strings. uniqueId is per-message (also 16-char uppercase). mentions arrive already resolved to PN; senderDevice is detected.
- Events render in GROUPS only.
client.send(jid).event({...}) does NOT show up in 1:1 DMs — only in @g.us groups. startAt/endAt accept a Date or epoch ms.
groupInvite.expiresAt is unix SECONDS (passing ms makes the card show "expired"). The card may fail to resolve on linked-device/LID-group sessions even when it renders — the chat.whatsapp.com/<code> link always works, so include it as a fallback.
- product/order/payment are receive-mostly. All three can be RECEIVED (
ctx.media variants), but only product can be SENT — and only from a WhatsApp Business account.
- ☁️ On cloud, respect the platform.
provider:'cloud' has no QR/socket — mount wa.webhook() on a public URL and subscribe the messages field. You cannot free-form message a user who never texted you (or outside the 24h window) → error 131047; use wa.sendTemplate(). Template parameters must match the {{n}} count exactly (132000). group/newsletter/edit/delete/AIRich/polls throw UNSUPPORTED_ON_CLOUD. Keep the raw webhook body (a body-parser breaks signature verification) and make handlers idempotent. Full rules → references/cloud.md.
Quick API cheat-sheet
import { Client } from 'zaileys'
const client = new Client({ authType: 'qr' })
client.on('qr', ({ qrString }) => console.log('Scan QR:', qrString))
client.on('connect', ({ me }) => console.log('Connected as', me.id))
client.on('disconnect', ({ reason, willReconnect }) => console.log('Down:', reason, willReconnect))
client.on('message', async (msg) => {
if (msg.isFromMe) return
const convoKey = msg.staticId
await msg.reply(`You said: ${msg.text}`)
})
await client.send(jid).text('hello')
await client.send(jid).image(bufferOrUrlOrPath, { caption: 'pic' })
await client.send(jid).text(markdown, { rich: true, title: '🤖', footer: 'zaileys' })
await client.send(jid).buttons([{ id: 'yes', text: 'Yes' }], { text: 'Pick' })
await client.send(jid).videoNote(srcMp4)
await client.send(groupJid).event({ name: 'Meetup', startAt: new Date(), call: 'video' })
await client.send(jid).groupInvite({ jid: groupJid, code, subject: 'My Group', expiresAt: Math.floor(Date.now()/1000)+86400 })
await client.send(jid).product({ image, title: 'Hat', businessOwnerId: myJid, price: 50, currency: 'USD' })
await client.send(jid).requestPhoneNumber()
await client.send(jid).text('tag').reply(quotedKey).mentions(['628x@s.whatsapp.net'])
await client.edit(key).text('edited')
await client.react(key, '🔥')
await client.delete(key, { forEveryone: true })
await client.forward(key, otherJid)
await client.pin(key, { duration: 604800 })
await client.unpin(key)
await client.setDisappearing(jid, 604800)
const pn = await client.lidToPn(lidJid)
const lid = await client.pnToLid(pnJid)
await client.broadcast(recipients, (b) => b.text('hi'), { rateLimitPerSec: 5, onProgress: (d, t, jid, ok) => {} })
Builder content methods: text · image · video · videoNote · audio · document · sticker · location · contact · poll · album · buttons · template · list · carousel · event · groupInvite · product · requestPhoneNumber · sharePhoneNumber · limitSharing. Modifiers: reply · mentions · mentionAll · disappearing · to. (event GROUPS-only; product Business-only.)
Events: message (umbrella — fires once for ANY inbound message → MessageContext); connection (qr, pairing-code, connect, reconnecting, disconnect, auth-exhausted, error); messages (text, image, video, audio, sticker, document, reaction, edit, delete, poll-vote); interactive (button-click, list-select); group/social (group-update, group-join, group-leave, member-tag, mention, mention-all); calls (call-incoming, call-ended); plus history-sync, presence, newsletter, limited. chatType covers text · image · video · audio · document · sticker · poll · contact · location · live-location · event · album · group-invite · product · order · payment · buttons · list · interactive · template · unknown.
MessageContext fields: identity uniqueId (16-char upper hex, per-message) · staticId (16-char upper hex, stable per room+sender — use as convo key) · channelId · chatId · receiverId · roomId · senderId · senderLid · senderName · senderDevice · timestamp · text · mentions (resolved to PN) · links. Flags: isFromMe · isGroup · isNewsletter · isBroadcast · isViewOnce · isEphemeral · isForwarded · isQuestion · isPrefix · isTagMe · isEdited · isDeleted · isPinned · isUnPinned · isBot · isSpam · isHideTags · isStatusMention · isGroupStatusMention · isStory. Methods: roomName() · receiverName() · replied() · message() · reply() · react() · citation. ctx.media variants: media attachment · poll · contact · location · event · album · group-invite · product · order · payment · link · buttons · list · interactive · template.
Domain modules (client.*):
group — create/addMember/removeMember/promote/demote/updateSubject/updateDescription/leave/metadata/tagMember/inviteCode/revokeInvite/acceptInvite/toggleEphemeral/setting · + list/inviteInfo/joinRequests/approveJoin/rejectJoin/joinApproval/memberAddMode
newsletter — create/follow/unfollow/metadata/updateName/updateDescription/updatePicture/mute/unmute/delete · + react/unreact/subscribers/messages/adminCount/changeOwner/demote/removePicture
community — create/createGroup/linkGroup/unlinkGroup/subGroups/leave/updateSubject/updateDescription/inviteCode/revokeInvite/acceptInvite · + metadata/list/inviteInfo/toggleEphemeral/setting/memberAddMode/joinApproval
privacy · presence
profile — setName/setStatus/setPicture/removePicture/getPicture/getStatus
chat — archive/unarchive/pin/unpin/mute/unmute/markRead/markUnread/star/unstar/delete/clear
contact — check/exists/save/remove
business (Business account) — profile/catalog/collections/orderDetails/createProduct/updateProduct/deleteProduct
Message mutations on client: pin(key,{duration?}) · unpin(key) · setDisappearing(jid, seconds) · lidToPn(lid) · pnToLid(pn) (both async).
Utils (from 'zaileys'): JID — isJid · normalizeJid · isLidJid · isPnJid · jidToPhone · phoneToJid · jidDecode · jidEncode · jidNormalizedUser · areJidsSameUser · isJidGroup · isJidBroadcast · isJidNewsletter · isLidUser · isPnUser · getDevice. Context/media — computeUniqueId · computeStaticId · extractLinks · senderDeviceOf · epochSecondsToMs · loadMedia · detectMimeFromBuffer. Misc — chunk.
Error classes (all carry .code): ZaileysBuilderError, ZaileysCommandError, ZaileysDomainError, ZaileysAutomationError, ZaileysStoreError. → see references/errors.md.
When debugging zaileys
- Is it an exception with a class/
.code? → references/errors.md: match the code → cause → fix.
- Is it a runtime symptom (QR keeps regenerating, reconnect loop, "module not found", ESM error)? → references/troubleshooting.md.
- Is it "works but wrong" (no autocomplete of an option, message not sending, double-handling own messages)? → references/pitfalls.md for the anti-pattern.
- Always verify the API exists before suggesting it — check references/api.md; never invent methods or options.
When implementing zaileys
- Start from the closest pattern in references/recipes.md.
- Apply the golden rules above.
- Prefer the typed event/context API over raw Baileys access.
- Keep examples runnable: real JIDs from env, awaited keys, single content method, rate-limited bulk.
Live docs (fetch for the latest)
These are authoritative and kept in sync with the code — fetch them when you need more detail, the newest API, or to verify before answering (do not guess when unsure):