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://zeative.github.io/zaileys/. 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 | β | β |
| 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 suite so your AI assistant writes, reviews, and debugs zaileys code with best practices β it knows the exact API, common pitfalls, and how to fix errors. Install it straight from this repo:
# Claude Code (native plugin β supports auto-update)
/plugin marketplace add zeative/zaileys
/plugin install zaileys-official@zeative
# npx skills (multi-agent: Claude Code, Codex, Cursor, OpenCode)
npx skills add zeative/zaileys # add -g for a global installThe suite has an orchestrator that auto-routes plus focused scaffold, debug, and review skills. See the full guide β zeative.github.io/zaileys/skill.
- 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, scheduled sends, and lazy media processing (image/video/audio/sticker).
- Runs everywhere β dual ESM/CJS with
.d.ts+.d.ctstypes; verified on Node, Bun, Deno, and Termux. - Modern foundation β Baileys
7.0.0-rc13(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.
await client.send(jid).text(
[
'*Daily brief* β',
'',
'```ts',
"const client = new Client()",
'```',
'',
':::suggest',
'See changelog | Upgrade guide',
':::',
].join('\n'),
{ rich: true, title: 'π° zaileys' },
)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.
- π zeative.github.io/zaileys β full documentation site: guides, API reference, recipes
- π€ AI Skill β official Claude Code /
npx skillsskill - π¦ 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.