### First Start Output with QR Code Source: https://zaileys.kejaa.id/recipes/convex-store Example output on first start, showing the QR code prompt and successful connection. ```text [zaileys] Connecting to WhatsApp (session: default)... [zaileys] Scan the QR code above with WhatsApp > Linked devices to authenticate. [zaileys] Connected as 6281234567890@s.whatsapp.net. ``` -------------------------------- ### Mount webhook on Node.js http server (setup) Source: https://zaileys.kejaa.id/cloud/webhook Starts mounting the webhook on a Node.js http server. This snippet shows the setup up to creating the webhook handler; the server creation and mounting code is not shown in the source. ```bash npm install zaileys ``` ```ts import { createServer } from 'node:http' import { Client } from 'zaileys' const client = new Client({ provider: 'cloud', cloud: { accessToken: process.env.WA_TOKEN!, phoneNumberId: process.env.WA_PHONE_ID!, verifyToken: process.env.WA_VERIFY_TOKEN!, appSecret: process.env.WA_APP_SECRET!, }, }) client.on('text', async (msg) => { await msg.reply(`You said: ${msg.text}`) }) const webhook = client.webhook() ``` -------------------------------- ### Install sharp with yarn Source: https://zaileys.kejaa.id/installation Install sharp using yarn. ```bash yarn add sharp ``` -------------------------------- ### Install sharp with bun Source: https://zaileys.kejaa.id/installation Install sharp using bun. ```bash bun add sharp ``` -------------------------------- ### Install sharp with pnpm Source: https://zaileys.kejaa.id/installation Install sharp using pnpm. ```bash pnpm add sharp ``` -------------------------------- ### Install Convex package Source: https://zaileys.kejaa.id/data/storage Installs the Convex client, needed by the Convex stores. ```bash npm install convex ``` -------------------------------- ### Install Zaileys with Bun Source: https://zaileys.kejaa.id/data/runtimes Install Zaileys using Bun's package manager. ```bash bun add zaileys ``` -------------------------------- ### Install sharp with npm Source: https://zaileys.kejaa.id/installation Install the sharp package using npm. sharp is optional; Zaileys uses it if present, otherwise falls back to jimp. ```bash npm install sharp ``` -------------------------------- ### Deno bot.ts example Source: https://zaileys.kejaa.id/data/runtimes Example bot using the npm: prefix to import Zaileys in Deno. Shows how to create a client and reply to text messages. ```ts import { Client } from 'npm:zaileys' const client = new Client() client.on('text', async (msg) => { await msg.reply(`You said: ${msg.text}`) }) ``` -------------------------------- ### Postgres adapter: install pg Source: https://zaileys.kejaa.id/data/storage Install the pg package for Postgres support. ```bash npm install pg ``` -------------------------------- ### start: Begin playback Source: https://zaileys.kejaa.id/templates/drum-pad Starts playback, initializing the scheduler and animation loop, and resumes the audio context if needed. ```javascript function start() { if (!unlock()) return; S.playing = true; S.step = 0; queue = []; var go = function () { if (!S.playing) return; S.nextTime = ac.currentTime + .06; scheduler(); timer = setInterval(scheduler, 25); raf = requestAnimationFrame(draw); }; if (ac.state === "running") go(); else ac.resume().then(go); renderTransport(); } ``` -------------------------------- ### cur, dur, elapsed helpers Source: https://zaileys.kejaa.id/templates/music-player Helper functions to get the current track, its duration, and the elapsed playback time (either from the session start or the stored position). ```javascript function cur() { return tracks[P.i]; } function dur(tr) { tr = tr || cur(); return tr.dur == null ? NaN : tr.dur; } function elapsed() { if (P.playing && sess) return clamp(ac.currentTime - sess.t0, 0, dur(sess.tr)); return P.pos; } ``` -------------------------------- ### Install Redis client (redis@4) Source: https://zaileys.kejaa.id/data/storage Installs Redis version 4, required before using RedisAuthStore and RedisMessageStore. ```bash npm install redis@4 ``` -------------------------------- ### Set API key and start server Source: https://zaileys.kejaa.id/recipes/express Sets the API_KEY environment variable and starts the server with tsx. The server will display a QR code for authentication. ```bash export API_KEY="replace-with-a-long-random-string" npx tsx server.ts ``` -------------------------------- ### Install Zaileys with yarn Source: https://zaileys.kejaa.id/data/runtimes Install Zaileys using yarn, another package manager. ```bash yarn add zaileys ``` -------------------------------- ### Install better-sqlite3 driver Source: https://zaileys.kejaa.id/concepts/configuration Install the better-sqlite3 package to enable SQLite storage for sessions and messages. ```bash npm install better-sqlite3 ``` -------------------------------- ### Install Zaileys and Convex Source: https://zaileys.kejaa.id/recipes/convex-store Installs the Zaileys and Convex packages required for the project. ```bash npm install zaileys convex ``` -------------------------------- ### Install Zaileys with pnpm Source: https://zaileys.kejaa.id/data/runtimes Install Zaileys using pnpm, an alternative package manager. ```bash pnpm add zaileys ``` -------------------------------- ### Install Zaileys with npm Source: https://zaileys.kejaa.id/data/runtimes Install Zaileys using npm. This is the standard package manager for Node.js. ```bash npm install zaileys ``` -------------------------------- ### Install better-sqlite3@11 with bun Source: https://zaileys.kejaa.id/installation Install the SQLite driver for Zaileys using bun. Keep the version tag because Zaileys supports better-sqlite3 11. ```bash bun add better-sqlite3@11 ``` -------------------------------- ### Install better-sqlite3@11 with npm Source: https://zaileys.kejaa.id/installation Install the SQLite driver for Zaileys. Keep the version tag because Zaileys supports better-sqlite3 11. ```bash npm install better-sqlite3@11 ``` -------------------------------- ### Help menu output example (text) Source: https://zaileys.kejaa.id/bots/commands Shows the expected reply when sending /help or /menu, with category names in bold and each command listed with its usage and description. ```text *general* /ping โ€” Check the bot is online /help โ€” Show this menu *fun* /dice [sides] โ€” Roll a die ``` -------------------------------- ### Reconnection Output Without QR Code Source: https://zaileys.kejaa.id/recipes/convex-store Example output on subsequent starts, showing connection without a QR code when using the same CONVEX_URL. ```text [zaileys] Connecting to WhatsApp (session: default)... [zaileys] Connected as 6281234567890@s.whatsapp.net. ``` -------------------------------- ### webhook() handler for Next.js API route (app/api/whatsapp/route.ts) Source: https://zaileys.kejaa.id/reference/client Example of using client.webhook() as a Next.js route handler. The returned function can handle GET and POST requests. ```typescript const webhook = client.webhook() export const GET = webhook export const POST = webhook ``` -------------------------------- ### Create a Client and listen for the connect event Source: https://zaileys.kejaa.id/concepts/client Initializes a new Client and logs the bot's ID when the connection is established. Use this as the foundation for any bot. ```ts const client = new Client() client.on('connect', ({ me }) => { console.log(`Ready as ${me.id}`) }) ``` -------------------------------- ### Log session ID and state Source: https://zaileys.kejaa.id/reference/client Use the sessionId property to get the session identifier, which defaults to 'default'. This example logs the session ID and current state. ```typescript console.log(`Session ${client.sessionId} is ${client.state}`) ``` -------------------------------- ### Expected server startup output Source: https://zaileys.kejaa.id/recipes/express Shows the expected output when the server starts, including the HTTP API URL and WhatsApp connection status. The order of lines can vary. ```text HTTP API listening on http://127.0.0.1:3000 [zaileys] Connecting to WhatsApp (session: default)... [zaileys] Scan the QR code above with WhatsApp > Linked devices to authenticate. ``` -------------------------------- ### Initialize playlist and player state Source: https://zaileys.kejaa.id/templates/music-player Initializes the playlist from DATA.playlist or a fallback example track, normalizes each track, sets up the AudioContext (with webkit fallback), and declares global state variables including the player state object P. ```javascript var list = DATA && DATA.playlist && DATA.playlist.length ? DATA.playlist : [{ title: "Example Track", artist: "Zaileys Synth", bpm: 100, mood: "happy" }]; var tracks = list.map(normalize); var AC = window.AudioContext || window.webkitAudioContext; var ac = null, chain = null, analyser = null, freq = null, sess = null, timer = 0, raf = 0, suspendT = 0, dragging = false, dragFrac = 0, page = 0; var P = { i: 0, playing: false, pos: 0, shuffle: false, repeat: 0, vol: .7, muted: false, history: [], status: TEXT.tap }; var $ = function (id) { return document.getElementById(id); }; ``` -------------------------------- ### scheduleAt() usage example Source: https://zaileys.kejaa.id/reference/client Shows how to schedule a message using the scheduleAt() method. The builder must start with .to(jid). The handle logs the scheduled job ID. ```ts const handle = await client.scheduleAt(new Date('2026-12-24T09:00:00+07:00'), (b) => b.to('6281234567890@s.whatsapp.net').text('Merry Christmas from Toko Budi!'), ) console.log('Scheduled job', handle.id) ``` -------------------------------- ### event() usage example Source: https://zaileys.kejaa.id/reference/send-builder Sends an event invitation with a name, start and end times, and a location. Requires a valid group JID. Rejects with SEND_FAILED on the Cloud API. ```typescript await client.send('120363025246125486@g.us').event({ name: 'Monthly team dinner', startAt: new Date('2026-10-02T19:00:00+07:00'), endAt: new Date('2026-10-02T21:00:00+07:00'), location: { latitude: -6.2297, longitude: 106.8295, name: 'Plaza Senayan' }, }) ``` -------------------------------- ### Music player playback control functions (beginPlayback, decodeClip, play, pause, setTrack, changeTrack, seekTo, setStatus, hexA) Source: https://zaileys.kejaa.id/templates/music-player Core playback control functions for the music player: beginPlayback starts or resumes playback, decodeClip decodes an audio clip from base64, play/pause toggle playback, setTrack/changeTrack switch tracks, seekTo seeks within the track, setStatus updates the status text, and hexA converts a hex color to rgba. These functions rely on shared state P, the AudioContext ac, and helper functions like cur(), render(), startSession(), stopSession(), and loop(). ```javascript function beginPlayback(pos) { var tr = cur(), idx = P.i; P.playing = true; P.pos = pos; if (tr.audio && !tr.buffer && !tr.failed) { setStatus(TEXT.loading); render(); decodeClip(tr, function () { if (P.i === idx && P.playing && !sess) beginPlayback(P.pos); }); return; } setStatus(tr.failed ? TEXT.clipFail : TEXT.playing); var go = function () { if (P.i === idx && P.playing) { startSession(pos); render(); loop(); } }; if (ac.state === "running") go(); else ac.resume().then(go); render(); } ``` ```javascript function decodeClip(tr, done) { var ok = function (buf) { tr.buffer = buf; tr.dur = buf.duration; done(); }; var bad = function () { tr.failed = true; tr.song = buildSong(tr); tr.dur = tr.song.steps * tr.song.stepDur; setStatus(TEXT.clipFail); if (cur() === tr) drawCover($("cover"), tr, 184); done(); }; try { var bin = atob(tr.audio), bytes = new Uint8Array(bin.length); for (var i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); var p = ac.decodeAudioData(bytes.buffer, ok, bad); if (p && p.catch) p.catch(function () {}); } catch (e) { bad(); } } ``` ```javascript function play() { if (!ensureAudio()) return; beginPlayback(P.pos >= dur() ? 0 : P.pos); } ``` ```javascript function pause() { P.pos = elapsed(); P.playing = false; stopSession(.06); setStatus(TEXT.paused); suspendLater(250); render(); } ``` ```javascript function setTrack(i) { P.i = i; P.pos = 0; var tr = cur(), c = MOOD_COLORS[tr.style]; document.documentElement.style.setProperty("--tint", hexA(c[0], .42)); drawCover($("cover"), tr, 184); } ``` ```javascript function changeTrack(i, keepHistory) { var wasPlaying = P.playing; if (keepHistory) P.history.push(P.i); if (sess) stopSession(.05); setTrack(i); if (wasPlaying) beginPlayback(0); else { if (P.status === TEXT.end) setStatus(TEXT.tap); render(); } } ``` ```javascript function seekTo(sec) { var d = dur(); if (!isFinite(d)) return; sec = clamp(sec, 0, Math.max(0, d - .05)); P.pos = sec; if (P.playing && sess) startSession(sec); render(); } ``` ```javascript function setStatus(s) { P.status = s; $("status").textContent = s; } ``` ```javascript function hexA(hex, a) { var n = parseInt(hex.slice(1), 16); return "rgba(" + (n >> 16) + "," + ((n >> 8) & 255) + "," + (n & 255) + "," + a + ")"; } ``` -------------------------------- ### contact() usage example Source: https://zaileys.kejaa.id/reference/send-builder Sends a contact card from a vCard string. The vCard must start with BEGIN:VCARD; the Cloud API reads the FN, N, and TEL lines. ```typescript const vcard = `BEGIN:VCARD VERSION:3.0 FN:Budi Santoso TEL;type=CELL;waid=6281234567890:+62 812-3456-7890 END:VCARD` await client.send(jid).contact(vcard) ``` -------------------------------- ### Directive syntax example Source: https://zaileys.kejaa.id/messaging/rich-responses Shows the general syntax for a directive block: starts with `:::name`, ends with `:::`, and each line is an item with optional leading `-` or `*`. Fields are `key: value` pairs separated by `|`. ```text :::product - title: Sneakers Blue | price: Rp450.000 | image: https://example.com/shop/sneakers-blue.jpg - title: Sneakers White | price: Rp450.000 | image: https://example.com/shop/sneakers-white.jpg ::: ``` -------------------------------- ### Plugin setup with commands and timer (plugins/notes.ts) Source: https://zaileys.kejaa.id/bots/plugins Registers 'note add' and 'note list' commands, stores notes in a Map, and clears them daily with a timer. Returns clearInterval so reloading doesn't stack timers. If setup throws, registrations are undone and the plugin is skipped. ```typescript const notes = new Map() export default definePlugin({ name: 'notes', setup(ctx) { ctx.command({ name: 'note add', description: 'Save a note', usage: '' }, async (cmd) => { const list = notes.get(cmd.senderId) ?? [] list.push(cmd.args.join(' ')) notes.set(cmd.senderId, list) await cmd.reply(`Saved. You have ${list.length} note(s).`) }) ctx.command({ name: 'note list', description: 'List your notes' }, async (cmd) => { const list = notes.get(cmd.senderId) ?? [] if (list.length === 0) { await cmd.reply('No notes yet.') return } let text = '' let number = 1 for (const note of list) { text += `${number}. ${note}\n` number += 1 } await cmd.reply(text.trim()) }) const daily = setInterval(() => notes.clear(), 24 * 60 * 60 * 1000) return () => clearInterval(daily) }, }) ``` -------------------------------- ### Using ClientEventMap for payload types Source: https://zaileys.kejaa.id/concepts/events Shows how to use `ClientEventMap` to get the payload type for any event by name. The example handles a `group-join` event and sends a welcome message with participant names. ```TypeScript const welcome = async (event: ClientEventMap['group-join']) => { const names = event.participants.map((p) => p.jid).join(', ') await client.send(event.groupId).text(`Welcome, ${names}!`) } client.on('group-join', welcome) ``` -------------------------------- ### Create a Cloud API client with credentials Source: https://zaileys.kejaa.id/cloud/setup Read the environment variables and create a `Client` with the `cloud` provider. The `wabaId` is optional and only needed for account-level calls. ```ts import { Client } from 'zaileys' const client = new Client({ provider: 'cloud', cloud: { accessToken: process.env.WA_TOKEN!, phoneNumberId: process.env.WA_PHONE_ID!, wabaId: process.env.WA_WABA_ID, verifyToken: process.env.WA_VERIFY_TOKEN!, appSecret: process.env.WA_APP_SECRET!, }, }) ``` -------------------------------- ### Handle poll-vote event Source: https://zaileys.kejaa.id/reference/events Listens for the 'poll-vote' event. The example uses the options() method to get readable option text and logs the voter's phone number or JID along with the picked options. ```ts client.on('poll-vote', async (vote) => { const picked = await vote.options() console.log(`${vote.voter.pn ?? vote.voter.jid} voted for ${picked.join(', ') || 'nothing we can read'}`) }) ``` -------------------------------- ### Drum Pad Configuration and Audio Setup Source: https://zaileys.kejaa.id/templates/drum-pad Defines the drum pad colors, emojis, step count, BPM range, example pattern, and SVG icons. Initializes the AudioContext and global state variables for the sequencer. ```javascript var PADS = [ { name: "Kick", emoji: "๐Ÿฅ", a: "#ff8a3d", b: "#a8284f", d: "#a8284f" }, { name: "Snare", emoji: "๐Ÿ‘", a: "#ff8a3d", b: "#ffc93c", d: "#b85a1e" }, { name: "Clap", emoji: "๐Ÿ‘", a: "#ffc93c", b: "#9be15d", d: "#a57f12" }, { name: "Hi-hat", emoji: "๐ŸŽฉ", a: "#9be15d", b: "#2ed8a7", d: "#4f8f2a" }, { name: "Open hat", emoji: "๐Ÿ”„", a: "#2ed8a7", b: "#38b6ff", d: "#17866a" }, { name: "Tom", emoji: "๐Ÿฅ", a: "#38b6ff", b: "#7c5cff", d: "#1f6fae" }, { name: "Cowbell", emoji: "๐Ÿฎ", a: "#7c5cff", b: "#b44cff", d: "#4b2fb0" }, { name: "Rim", emoji: "๐Ÿฅ", a: "#b44cff", b: "#ff4f8b", d: "#7422b0" }, { name: "Sparkle", emoji: "โœจ", a: "#ffc93c", b: "#ff4f8b", d: "#b0355e" } ]; var STEPS = 8, MIN_BPM = 60, MAX_BPM = 180; var EXAMPLE = [[0, 4, 5], [2, 6], [], [0, 1, 2, 3, 4, 5, 6], [7], [], [], [], []]; var ICON = { vol: '', mute: '', play: '', stop: '' }; var AC = window.AudioContext || window.webkitAudioContext; var ac = null, master = null, comp = null, timer = 0, raf = 0, openHat = null; var S = { bpm: 110, muted: false, playing: false, step: 0, nextTime: 0, screen: "pads", now: -1 }; var pattern = [], queue = [], padEls = [], cellEls = []; var stats = { voices: 0 }; var $ = function (id) { return document.getElementById(id); }; function buildChain(c) { var m = c.createGain(), k = c.createDynamicsCompressor(); k.threshold.value = -10; k.knee.value = 8; k.ratio.value = 4; k.attack.value = .002; k.release.value = .15; m.connect(k); k.connect(c.destination); return { master: m, comp: k }; } function noiseBuf(c) { if (c.zNoise) return c.zNoise; var b = c.createBuffer(1, c.sampleRate, c.sampleRate), d = b.getChannelData(0); for (var i = 0; i < d.length; i++) d[i] = Math.random() * 2 - 1; return (c.zNoise = b); } function osc(c, out, type, f, t, vol, decay, o) { o = o || {}; var n = c.createOscillator(), g = c.createGain(); n.type = type; n.frequency.setValueAtTime(f, t); if (o.to) n.frequency.exponentialRampToValueAtTime(o.to, t + (o.toT || .1)); g.gain.setValueAtTime(0, t); g.gain.linearRampToValueAtTime(vol, t + (o.a || .002)); g.gain.exponentialRampToValueAtTime(.0005, t + decay); n.connect(g); g.connect(out); n.start(t); n.stop(t + decay + .03); stats.voices++; return g; } function noise(c, out, t, vol, decay, type, f, q) { var s = c.createBufferSource(), fl = c.createBiquadFilter(), g = c.createGain(); s.buffer = noiseBuf(c); fl.type = type; fl.frequency.value = f; fl.Q.value = q || .7; g.gain.setValueAtTime(0, t); g.gain.linearRampToValueAtTime(vol, t + .002); g.gain.exponentialRampToValueAtTime(.0005, t + decay); s.connect(fl); fl.connect(g); g.connect(out); s.start(t, Math.random() * .5); s.stop(t + decay + .03); stats.voices++; return g; } function choke(t) { if (openHat) { openHat.gain.cancelScheduledValues(t); openHat.gain.setTargetAtTime(0, t, .012); openHat = null; } } var SOUNDS = [ function (c, out, t) { osc(c, out, "sine", 155, t, .95, .42, { to: 45, toT: .14 }); osc(c, out, "triangle", 420, t, .25, .04, { to: 90, toT: .03 }); }, function (c, out, t) { noise(c, out, t, .75, .2, "bandpass", 1900, .6); osc(c, out, "triangle", 190, t, .4, .12, { to: 120, toT: .08 }); } ]; ``` -------------------------------- ### Next.js Cloud API bot (route handler) Source: https://zaileys.kejaa.id/recipes/cloud-servers Complete Next.js route handler for a Cloud API bot. Installs zaileys and defines the route in app/api/whatsapp/route.ts. It handles the webhook at /api/whatsapp, using GET for verification and POST for messages. ```bash npm install zaileys ``` ```tsx import { Client } from 'zaileys' const client = new Client({ provider: 'cloud', cloud: { accessToken: process.env.WA_TOKEN!, phoneNumberId: process.env.WA_PHONE_ID!, verifyToken: process.env.WA_VERIFY_TOKEN!, appSecret: process.env.WA_APP_SECRET!, }, }) client.on('error', ({ error }) => console.error('Zaileys error:', error.message)) client.on('text', async (msg) => { try { await client.markRead(msg.chatId, { typing: true }) await msg.reply(`You said: ${msg.text}`) } catch (error) { console.error('reply failed:', error) } }) client.on('message-status', (status) => { console.log(`message ${status.id} is ${status.status}`) }) const webhook = client.webhook() export async function GET(req: Request) { const url = new URL(req.url) const response = await webhook(new Request(url, { method: 'GET', headers: req.headers })) return new Response(response.body, { status: response.status, headers: response.headers }) } export async function POST(req: Request) { const response = await webhook(req) return new Response(response.body, { status: response.status, headers: response.headers }) } ``` -------------------------------- ### Get raw message with msg.message() Source: https://zaileys.kejaa.id/concepts/message-context Use `msg.message()` to retrieve the original Baileys message object, needed for quoting with the send builder and accessing details not surfaced by Zaileys. The example shows sending an image with a reply to the raw message. ```TypeScript client.on('text', async (msg) => { if (msg.text === 'chart') { await client.send(msg.roomId ?? msg.senderId).image('./chart.png').reply(msg.message()) } }) ``` -------------------------------- ### Unofficial provider startup output Source: https://zaileys.kejaa.id/quickstart Expected terminal output when starting the bot: shows connecting and QR code scan instructions. ```text [zaileys] Connecting to WhatsApp (session: default)... [zaileys] Scan the QR code above with WhatsApp > Linked devices to authenticate. ``` -------------------------------- ### Complete example: create community, groups, and invite link Source: https://zaileys.kejaa.id/groups/communities Sets up a community with two groups on first connect and prints the invite link. Each createGroup() call waits 120 seconds, so the whole setup takes about four minutes. ```ts const client = new Client() const NAME = 'Bandung Outdoor Club' client.on('connect', async () => { const communities = await client.community.list() if (communities.some((community) => community.subject === NAME)) return const community = await client.community.create(NAME, 'Hiking and cycling groups in Bandung.') await client.community.createGroup('Hiking', ['6281234567890@s.whatsapp.net'], community.id) await client.community.createGroup('Cycling', ['6289876543210@s.whatsapp.net'], community.id) const groups = await client.community.subGroups(community.id) console.log('Groups:', groups.map((group) => group.subject).join(', ')) const code = await client.community.inviteCode(community.id) if (code) console.log(`Invite link: https://chat.whatsapp.com/${code}`) }) ``` -------------------------------- ### zaileys v3 client options example Source: https://zaileys.kejaa.id/releases/migration Example of the v3 client options, showing the old option names like 'session', 'prefix', and 'phoneNumber' as a number. This is the before state for the migration. ```ts const wa = new Client({ session: 'zaileys', authType: 'pairing', phoneNumber: 6281234567890, prefix: '/', showLogs: true, }) ``` -------------------------------- ### Handle 'error' event Source: https://zaileys.kejaa.id/reference/events Listens for the 'error' event, which fires when the automatic connect started by new Client() fails (e.g., because a database package for your store isn't installed). It's only emitted while something listens; otherwise the error goes to the logger. ```ts client.on('error', ({ sessionId, error }) => { console.error(`[${sessionId}] could not connect:`, error.message) }) ``` -------------------------------- ### Memory adapter: in-memory auth and message stores Source: https://zaileys.kejaa.id/data/storage Uses MemoryAuthStore and MemoryMessageStore to keep data in memory. No installation required. Warning: MemoryAuthStore forgets the login on exit, requiring a new QR scan on every start; use for tests only. ```typescript import { Client, MemoryAuthStore, MemoryMessageStore } from 'zaileys' const client = new Client({ auth: new MemoryAuthStore(), store: new MemoryMessageStore(), }) ``` -------------------------------- ### Start the Bot with CONVEX_URL and npx tsx bot.ts Source: https://zaileys.kejaa.id/recipes/convex-store Sets the CONVEX_URL environment variable to the deployment URL (ending in .convex.cloud) and starts the bot with tsx. Then scan the QR code from WhatsApp > Settings > Linked devices > Link a device. ```bash export CONVEX_URL="https://happy-otter-123.convex.cloud" npx tsx bot.ts ``` -------------------------------- ### Initialize Card Data Source: https://zaileys.kejaa.id/templates/business-card Sets up the card with the provided data, filling in the name, role, and bio. If no name is provided, it shows a placeholder and sets the name color to muted. ```javascript var d = DATA || {}; function str(v) { return v === undefined || v === null ? '' : String(v).trim(); } function $(id) { return document.getElementById(id); } var name = str(d.name), role = str(d.role), company = str(d.company); var roleLine = role && company ? role + ' ยท ' + company : role || company; $('#name').textContent = name || TEXT.name; if (!name) $('#name').style.color = 'var(--muted)'; $('#role').textContent = roleLine; $('#backName').textContent = name || TEXT.name; $('#backRole').textContent = roleLine; if (str(d.bio)) $('#bioText').textContent = str(d.bio); else $('#bio').hidden = true; ``` -------------------------------- ### File adapter: default auth store Source: https://zaileys.kejaa.id/data/storage Uses FileAuthStore to persist login as JSON files. Requires no extra installation. The basePath is relative to the bot's start folder; default is './.zaileys/auth'. Files are written atomically to avoid corruption. ```typescript import { Client, FileAuthStore } from 'zaileys' const client = new Client({ auth: new FileAuthStore({ basePath: './sessions/support-bot' }), }) ``` -------------------------------- ### Express Cloud API bot Source: https://zaileys.kejaa.id/recipes/cloud-servers Complete Express server using zaileys. Installs deps, defines the client, handles text, marks read, replies, logs status, exposes /webhook, and starts on port 3000. Requires raw body handling. ```bash npm install zaileys express ``` ```tsx import express from 'express' import { Client } from 'zaileys' const client = new Client({ provider: 'cloud', cloud: { accessToken: process.env.WA_TOKEN!, phoneNumberId: process.env.WA_PHONE_ID!, verifyToken: process.env.WA_VERIFY_TOKEN!, appSecret: process.env.WA_APP_SECRET!, }, }) client.on('error', ({ error }) => console.error('Zaileys error:', error.message)) client.on('text', async (msg) => { try { await client.markRead(msg.chatId, { typing: true }) await msg.reply(`You said: ${msg.text}`) } catch (error) { console.error('reply failed:', error) } }) client.on('message-status', (status) => { console.log(`message ${status.id} is ${status.status}`) }) const webhook = client.webhook() const app = express() app.all('/webhook', express.raw({ type: '*/*' }), async (req, res) => { const headers = new Headers() for (const [name, value] of Object.entries(req.headers)) { if (typeof value === 'string') headers.set(name, value) } const response = await webhook( new Request(`http://localhost${req.originalUrl}`, { method: req.method, headers, body: req.method === 'POST' ? req.body : undefined, }), ) res.status(response.status).type('text/plain').send(await response.text()) }) app.listen(3000, () => console.log('Webhook listening on http://localhost:3000/webhook')) ``` -------------------------------- ### Postgres adapter: client setup with connection string Source: https://zaileys.kejaa.id/data/storage Uses PostgresAuthStore and PostgresMessageStore with a connection string from the DATABASE_URL environment variable. The auth store uses max: 5 for the connection pool. Tables are created on first use. ```typescript import { Client, PostgresAuthStore, PostgresMessageStore } from 'zaileys' const url = process.env.DATABASE_URL! const client = new Client({ auth: new PostgresAuthStore({ connectionString: url, max: 5 }), store: new PostgresMessageStore({ connectionString: url }), }) ``` -------------------------------- ### Get the message key with client.send(), msg.reply(), and msg.message().key Source: https://zaileys.kejaa.id/messaging/edit-delete-react Shows the three ways to obtain a message key: from the result of client.send(), from msg.reply(), and from the incoming message's own key. Save the key if you'll need it later, for example in your database next to the order it belongs to. ```ts // 1. Every send resolves to the key of the message it sent const sent = await client.send(jid).text('Your driver arrives in 10 minutes.') // 2. So does msg.reply() client.on('text', async (msg) => { const answer = await msg.reply('Checking your orderโ€ฆ') // 3. Every incoming message carries its own key const incoming = msg.message().key }) ``` -------------------------------- ### Hono Cloud API bot Source: https://zaileys.kejaa.id/recipes/cloud-servers Complete Hono server using zaileys. Installs deps, defines the client, handles text, marks read, replies, logs status, exposes /webhook, and starts on port 3000. For Bun, remove serve and export default app. ```bash npm install zaileys hono @hono/node-server ``` ```tsx import { serve } from '@hono/node-server' import { Hono } from 'hono' import { Client } from 'zaileys' const client = new Client({ provider: 'cloud', cloud: { accessToken: process.env.WA_TOKEN!, phoneNumberId: process.env.WA_PHONE_ID!, verifyToken: process.env.WA_VERIFY_TOKEN!, appSecret: process.env.WA_APP_SECRET!, }, }) client.on('error', ({ error }) => console.error('Zaileys error:', error.message)) client.on('text', async (msg) => { try { await client.markRead(msg.chatId, { typing: true }) await msg.reply(`You said: ${msg.text}`) } catch (error) { console.error('reply failed:', error) } }) client.on('message-status', (status) => { console.log(`message ${status.id} is ${status.status}`) }) const webhook = client.webhook() const app = new Hono() app.all('/webhook', (c) => webhook(c.req.raw)) serve({ fetch: app.fetch, port: 3000 }, () => { console.log('Webhook listening on http://localhost:3000/webhook') }) ``` -------------------------------- ### Run the bot with the unofficial provider Source: https://zaileys.kejaa.id/recipes/order-bot Starts the bot using the unofficial provider. Set OWNER_JID to your WhatsApp number. After scanning the QR code, the terminal prints a connection message. ```bash OWNER_JID="6281234567890@s.whatsapp.net" npx tsx bot.ts ``` ```text [zaileys] Connected as 6289876543210@s.whatsapp.net. ``` -------------------------------- ### Check installed version of zaileys with npm Source: https://zaileys.kejaa.id/releases/versioning Use this command to see which version of zaileys is installed in your project. The output will show the installed version. ```bash npm list zaileys ``` -------------------------------- ### Start schedule once on connect Source: https://zaileys.kejaa.id/recipes/scheduled-broadcast Starts the schedule only on the first 'connect' event. Without the started flag, reconnects would add multiple timers. ```TypeScript declare function scheduleNextRun(): void let started = false client.on('connect', () => { if (started) return started = true scheduleNextRun() }) ``` -------------------------------- ### Official provider ClientOptions example Source: https://zaileys.kejaa.id/reference/client-options Complete ClientOptions example for the Official (cloud) provider. Requires `provider: 'cloud'` and the `cloud` object with environment variables for access token, phone number ID, WABA ID, verify token, app secret, and API version. Options that only drive the WhatsApp Web connection (authType, reconnect, plugins) are accepted but do nothing. ```ts import { Client } from 'zaileys' const client = new Client({ provider: 'cloud', cloud: { accessToken: process.env.WA_TOKEN!, phoneNumberId: process.env.WA_PHONE_ID!, wabaId: process.env.WA_WABA_ID, verifyToken: process.env.WA_VERIFY_TOKEN, appSecret: process.env.WA_APP_SECRET, apiVersion: 'v23.0', }, sessionId: 'shop-cloud', ignoreMe: true, statusLog: true, }) ``` -------------------------------- ### bot.ts: Client setup with plugins and command-not-found handler Source: https://zaileys.kejaa.id/recipes/plugin-starter Creates a `Client` with a `commandPrefix` of `'!'` and a `plugins` config pointing to `./plugins`. Enables hot reload via `watch` unless `NODE_ENV` is `production`, and logs plugin load errors. Also registers a `command-not-found` event that replies with a fallback message. ```ts import { Client } from 'zaileys' const client = new Client({ commandPrefix: '!', plugins: { dir: './plugins', watch: process.env.NODE_ENV !== 'production', onError: (error, file) => console.error(`Could not load ${file}:`, error), }, }) client.on('command-not-found', ({ command, message }) => { message.reply(`There's no !${command} command. Try !hello.`).catch(console.error) }) ``` -------------------------------- ### Initialize client with wabaId for template management Source: https://zaileys.kejaa.id/cloud/templates Adds wabaId to the client configuration, required for template management operations. Without it, list(), create(), delete(), and get() by name throw ZaileysCloudError with code CONFIG: 'this operation needs cloud.wabaId (WhatsApp Business Account id)'. The WABA ID is found in Meta's dashboard under WhatsApp โ†’ API Setup. ```ts const client = new Client({ provider: 'cloud', cloud: { accessToken: process.env.WA_TOKEN!, phoneNumberId: process.env.WA_PHONE_ID!, wabaId: process.env.WA_WABA_ID!, }, }) ``` -------------------------------- ### Send a template with client.sendTemplate() Source: https://zaileys.kejaa.id/cloud/templates Sends the 'hello_world' template to a customer. The template must be approved and the language code must match an approved language. The method resolves to the message key, and key.id is Meta's message ID used for tracking delivery. ```ts const key = await client.sendTemplate('6281234567890', 'hello_world', 'en_US') console.log(key.id) // wamid.HBgNNjI4MTIzNDU2Nzg5MBUCABEYEjdF... ``` -------------------------------- ### Example command message with arguments and flags Source: https://zaileys.kejaa.id/bots/commands Shows a sample command message that includes quoted arguments, a flag with a value, and a boolean flag. ```text /remind "call mom" tomorrow --at=18:00 --urgent ``` -------------------------------- ### Run Deno bot with all permissions Source: https://zaileys.kejaa.id/data/runtimes Run the bot with -A to grant all permissions. Zaileys needs network, file, environment, and ffmpeg permissions. ```bash deno run -A bot.ts ``` -------------------------------- ### Warning message when sharp is not installed Source: https://zaileys.kejaa.id/messaging/receiving-media This text output is printed when the first image operation runs without sharp installed, indicating that Jimp is slow and suggesting to install sharp. ```text [media-process] Jimp is slow. For faster performance, run: npm install sharp ``` -------------------------------- ### new Client() Source: https://zaileys.kejaa.id/reference/client Creates a new Client instance. Works on both WhatsApp Web and Cloud API providers. ```APIDOC ## new Client() ### Description Creates a new Client instance. ### Method Constructor ### Endpoint `new Client()` ### Parameters None ### Returns `Client` ### Provider Support Both (WhatsApp Web and Cloud API) ``` -------------------------------- ### Startup connection logs Source: https://zaileys.kejaa.id/recipes/cloud-servers Expected log output when the server starts and connects to WhatsApp, showing the phone number ID. ```text [zaileys] Connecting to WhatsApp (session: default)... [zaileys] Connected as 106540352242922. ``` -------------------------------- ### Configure client with commandPrefix and plugins dir Source: https://zaileys.kejaa.id/releases/migration Configures the client to load plugins from a folder and sets the command prefix. Hot reload is on by default; disable with plugins: { watch: false }. ```ts const client = new Client({ commandPrefix: '/', plugins: { dir: './plugins' } }) ``` -------------------------------- ### SQLite adapter: client setup Source: https://zaileys.kejaa.id/data/storage Uses SqliteAuthStore and SqliteMessageStore with a shared database file. Tables are created on first use. The database runs in WAL mode, creating additional -wal and -shm files. The database path is required. ```typescript import { Client, SqliteAuthStore, SqliteMessageStore } from 'zaileys' const client = new Client({ auth: new SqliteAuthStore({ database: './whatsapp.db' }), store: new SqliteMessageStore({ database: './whatsapp.db' }), }) ``` -------------------------------- ### delete() usage example Source: https://zaileys.kejaa.id/reference/client Deletes a message. The example sends a message and then deletes it. ```typescript const key = await client.send(jid).text('Sorry, wrong chat') await client.delete(key) ``` -------------------------------- ### Install Zaileys skill with npx skills Source: https://zaileys.kejaa.id/ai Install the Zaileys skill into the current project for Claude Code, Codex, Cursor, OpenCode, and other agents. Add -g to install for every project on your machine. ```bash npx skills add zeative/zaileys ``` ```bash npx skills add zeative/zaileys -g ``` -------------------------------- ### Run the bot with the official provider Source: https://zaileys.kejaa.id/recipes/order-bot Starts the bot using the official Cloud API. Requires environment variables for the access token, phone ID, verify token, app secret, and owner JID. The webhook URL must be publicly reachable. ```bash export WA_TOKEN="your-access-token" export WA_PHONE_ID="your-phone-number-id" export WA_VERIFY_TOKEN="any-secret-string-you-choose" export WA_APP_SECRET="your-app-secret" export OWNER_JID="6281234567890@s.whatsapp.net" npx tsx bot.ts ``` -------------------------------- ### Run the official WhatsApp bot with environment variables Source: https://zaileys.kejaa.id/quickstart Set the four environment variables from your Meta app, then run the bot with your TypeScript runner. Make port 3000 reachable over HTTPS (e.g., `ngrok http 3000`) and configure the webhook in the Meta dashboard with the callback URL ending in `/webhook` and the verify token matching `WA_VERIFY_TOKEN`. ```bash export WA_TOKEN="your-access-token" export WA_PHONE_ID="your-phone-number-id" export WA_VERIFY_TOKEN="any-secret-string-you-choose" export WA_APP_SECRET="your-app-secret" npx tsx bot.ts ```