Zaileys is a type-safe WhatsApp framework for Node.js & TypeScript with two providers behind one API: the unofficial WhatsApp Web engine (Baileys) and the official Meta WhatsApp Cloud API. Write your bot once against a single chainable builder and typed events — then run it on either provider by flipping one option. Authentication, reconnection, and storage are handled for you.
Quick start • Providers • Why Zaileys • Install • What you can build • Storage • Runtimes • Docs
Note
This README is a high-level overview. The complete API reference, guides, and recipes live in the documentation site at https://zaileys.kejaa.id. Runnable code lives in examples/.
There is no await connect() — the client connects on construction, so every handler you register synchronously is wired up before the first event arrives.
import { Client } from 'zaileys'
const client = new Client()
client.on('qr', ({ qrString }) => console.log('Scan this QR:', qrString))
client.on('connect', ({ me }) => console.log('Connected as', me.id))
client.on('text', async (msg) => {
await msg.reply(`You said: ${msg.text}`)
})That is the whole bot. Scan the printed QR via WhatsApp → Linked Devices, and every text message gets a reply.
Prefer a pairing code? Provide your number:
const client = new Client({ authType: 'pairing', phoneNumber: '6281234567890' })Zaileys runs on either the unofficial WhatsApp Web engine or the official Meta WhatsApp Cloud API — same Client, same send(jid)… builder, same typed events. Switch with one option; your handlers never change.
// 🔗 Unofficial (default) — WhatsApp Web via Baileys. QR/pairing login, groups, polls, channels.
const client = new Client()
// ☁️ Official — Meta Cloud API. Token auth, no ban risk, templates/OTP/campaigns, Flows, commerce.
const client = new Client({
provider: 'cloud',
cloud: {
accessToken: process.env.WA_TOKEN!,
phoneNumberId: process.env.WA_PHONE_ID!,
verifyToken: process.env.WA_VERIFY!,
appSecret: process.env.WA_APP_SECRET!,
},
})
client.on('text', (m) => m.reply(`echo: ${m.text}`))
await client.sendTemplate('628xxx', 'welcome', 'en_US') // cloud-only: reach users who never texted you
// inbound arrives via webhook (framework-agnostic) — mount on any server:
export const GET = client.webhook()
export const POST = client.webhook()| 🔗 Unofficial (WhatsApp Web) | ☁️ Official (Meta Cloud API) | |
|---|---|---|
| Login | QR / pairing code, no approval | Permanent token |
| Ban risk | Exists | None (sanctioned) |
| Groups / channels / polls | ✅ | ❌ |
| Commands, plugins, broadcast, scheduled sends | ✅ | ❌ (need the WhatsApp Web connection) |
| Templates / OTP / marketing | ❌ | ✅ |
| Message users who never texted you | ✅ any number | ✅ via approved templates |
Pick your provider → Choose Your Provider · Official Cloud API guide.
Zaileys ships an official Agent Skill, zaileys, so your AI assistant builds, extends, debugs, reviews,
upgrades, and deploys zaileys bots with the real API instead of guessing. It covers both providers, includes
runnable templates and a read-only project doctor, and is checked against the source on every change.
Install it straight from this repo:
# Claude Code plugin
/plugin marketplace add zeative/zaileys
/plugin install zaileys-official@zeative
# npx skills (Claude Code, Codex, Cursor, OpenCode, and more)
npx skills add zeative/zaileys # add -g for a global installUpgrading from the four earlier skills (zaileys-assist, -scaffold, -debug, -review)? See
zaileys.kejaa.id/ai.
- Two providers, one codebase — the unofficial WhatsApp Web engine and the official Meta Cloud API behind the same
Client. Switch with a single option; your handlers never change. - Typed events —
on('text' | 'image' | 'reaction' | 'button-click' | 'group-update' | …)with fully-typed payloads and IntelliSense. No raw Baileys decoding, noany. - One chainable builder —
client.send(jid).text(…).reply(quoted).mentions([…])resolves to the sent message key when awaited. - Rich & interactive out of the box — native buttons, lists, carousels, and Meta-AI-style rich responses written as plain markdown.
- Auto lifecycle — QR or pairing-code login, auto-reconnect with backoff, clean logout, optional
ignoreMe. - Pluggable storage — independent
AuthStoreandMessageStoreinterfaces withfile,memory,sqlite,redis,postgres, andconvexadapters. - Batteries included — command framework, broadcast with rate limiting, and scheduled sends (WhatsApp Web only), plus lazy media processing (image/video/audio/sticker) on both providers.
- Runs everywhere — dual ESM/CJS with
.d.ts+.d.ctstypes; verified on Node, Bun, Deno, and Termux. - Modern foundation — Baileys
7.0.0-rc14(includes the CVE-2026-48063 spoofing patch), built and type-checked with the native (Go) TypeScript 7 compiler.
npm i zaileys # or: pnpm add zaileys • yarn add zaileys • bun add zaileysRequires Node.js v20+. The file auth store is the zero-config default and needs nothing else.
Termux / Android: install with
npm install zaileys --legacy-peer-deps. A plainnpm installtries to compilesharp(a peer dependency of Baileys, no prebuilt ARM binary) and fails; the flag skips it and Zaileys falls back to the bundledjimppath.pnpm/yarnare unaffected.
Other storage backends are optional peer dependencies — install only the one you use:
npm i better-sqlite3 # sqlite adapters
npm i redis # redis adapters
npm i pg # postgres adapters
npm i convex # convex adapterssharp is an optional accelerator for media/sticker processing; without it Zaileys falls back to a pure-JS path automatically. It is not a Zaileys dependency, but Baileys pulls it in as a peer — on platforms with no prebuilt binary (Termux/Android, some Alpine) install with --legacy-peer-deps to skip it.
await client.send(jid).text('Hello there')
await client.send(jid).image('https://example.com/photo.jpg', { caption: 'Nice shot' })
await client.send(jid).poll('Pick one', ['Red', 'Green', 'Blue'])
await client.send(jid).album([
{ type: 'image', src: './a.jpg' },
{ type: 'image', src: './b.jpg' },
])Reply, URL, copy, call, reminder, location, and address buttons — plus lists and carousels — rendered natively on personal accounts.
await client.send(jid).buttons(
[
{ id: 'yes', text: 'Yes' },
{ type: 'url', text: 'Open docs', url: 'https://github.com/zeative/zaileys' },
{ type: 'copy', text: 'Copy code', code: 'ZAILEYS-2026' },
],
{ title: 'Pick one', text: 'Tap a button below' },
)
client.on('button-click', (ctx) => console.log('tapped:', ctx.buttonId))Toggle { rich: true } and write ordinary markdown — fenced code (syntax-highlighted), tables, images, and ::: directives for products, suggestions, and more.
// The message contains backticks, so write it as a plain string with \n line breaks.
const brief = "*Daily brief* ☕\n\n```ts\nconst client = new Client()\n```\n\n:::suggest\nSee changelog | Upgrade guide\n:::"
await client.send(jid).text(brief, { rich: true, title: '📰 zaileys' })These run on the WhatsApp Web provider; on the Cloud API, handle text events and send in a loop instead.
const client = new Client({ commandPrefix: ['/', '!'] })
client.command('ping', (ctx) => ctx.reply('pong 🏓'))
await client.broadcast(jids, (b) => b.text('Announcement'), { rateLimitPerSec: 5 })
await client.scheduleAt(new Date(Date.now() + 60_000), (b) => b.text('Sends in 1 minute'))const key = await client.send(jid).text('Original')
await client.edit(key).text('Edited')
await client.react(key, '👍')
await client.delete(key, { forEveryone: true })
await client.forward(key, otherJid)Auth state and message history use two independent interfaces, so you can mix and match — e.g. auth in SQLite, messages in Redis.
import { Client, SqliteAuthStore, RedisMessageStore } from 'zaileys'
const client = new Client({
auth: new SqliteAuthStore({ database: './auth.db' }),
store: new RedisMessageStore({ url: 'redis://localhost:6379' }),
})| Adapter | Auth store | Message store | Peer dependency |
|---|---|---|---|
file |
FileAuthStore ⭐ |
— | none |
memory |
MemoryAuthStore |
MemoryMessageStore |
none |
sqlite |
SqliteAuthStore |
SqliteMessageStore |
better-sqlite3 |
redis |
RedisAuthStore |
RedisMessageStore |
redis |
postgres |
PostgresAuthStore |
PostgresMessageStore |
pg |
convex |
ConvexAuthStore |
ConvexMessageStore |
convex |
⭐ default. Convex requires deploying the helper functions in
examples/convex/— see that folder's README.
Zaileys ships dual ESM/CJS entry points with type declarations for both module systems, and is verified to load on:
| Runtime | ESM | CJS |
|---|---|---|
Node.js >=20 |
✅ | ✅ |
| Bun | ✅ | ✅ |
Deno (--node-modules-dir) |
✅ | ✅ |
| Termux (Android) | ✅ | ✅ |
Package managers: npm, pnpm, yarn, and bun are all supported.
bun buildfails with "Browser build cannot import Node.js builtin" — Zaileys (and baileys underneath) is a Node-only library; bun's bundler defaults to a browser target. Pass the target explicitly:bun build index.ts --target bun(or--target node).tscreports errors insidenode_modules(ws,thread-stream,whatsapp-rust-bridge) — upstream declaration issues, not yours. Set"skipLibCheck": truein yourtsconfig.json.
- 🌐 zaileys.kejaa.id — the documentation site
- Quickstart — a working bot in about five minutes
- Guides — messaging, media, buttons, commands, groups, storage
- Cloud API — webhook, templates, limits
- Recipes — complete bots you can copy
- Reference — every method, option, and event
- Feature matrix — what works on which provider
- 🤖 Build with AI — official agent skill for Claude Code, Codex, Cursor, and more
- 📦 examples/ — runnable bots: quickstart, interactive buttons, AIRich, storage adapters, broadcast
- 🔀 MIGRATION.md — upgrading from v3.x to v4.0.0 (breaking changes, side-by-side snippets)
- 🤝 CONTRIBUTING.md — dev setup, tests, commit convention, release flow
- 🔒 SECURITY.md — vulnerability disclosure and supported versions
- 📝 CHANGELOG.md — release history
Hit a problem or have a feature request? Open an issue.
- Buy me a coffee ☕ • Ko-Fi • Trakteer
- ⭐ Star the repo on GitHub
Distributed under the MIT License. See LICENSE for details.