Skip to Content
Configuration

Configuration

Every zaileys bot starts by constructing a Client with a ClientOptions object. This page is the exhaustive reference for every option the constructor accepts, the exact default it falls back to, and how each one changes the client’s behavior. All defaults below are read straight from the Client constructor source, not guessed.

import { Client } from 'zaileys' const client = new Client({ sessionId: 'default' })

ClientOptions is fully optional — new Client() works and applies every default in the table below. The most common reason to pass options is to pick a login method (authType), enable commands via commandPrefix, or swap in a persistent storage adapter via auth / store.

All options at a glance

OptionTypeDefaultDescription
sessionIdstring'default'Identifies this session. Used as the default auth folder name and as the sessionId field on every emitted event.
authType'qr' | 'pairing''qr'Login method: scan a QR code ('qr') or request an 8-digit pairing code ('pairing', requires phoneNumber).
phoneNumberstringundefinedPhone number in international digits (e.g. '628xxx'). Required when authType is 'pairing'; ignored for 'qr'.
authAuthStoreBundleFileAuthStore at ./.zaileys/auth/<sessionId>Where Baileys credentials and signal keys are persisted. See Storage Adapters.
storeMessageStoreMemoryMessageStoreWhere messages, chats, contacts, presence, and scheduled jobs are stored. See Storage Adapters.
loggerLogger | Partial<Logger>Pino logger, level from ZAILEYS_DEBUG (default silent)Structured logger with debug / info / warn / error / fatal. A partial object is padded with no-ops.
commandPrefixstring | string[]undefined (no prefixes → commands disabled)Trigger prefix(es) that activate the command framework, e.g. '/' or ['/', '!'].
ignoreMebooleantrueWhen true, inbound messages sent by your own account are dropped before reaching handlers.
citationCitationConfigundefinedConfigures the per-message citation.authors() / citation.banned() predicates available in message context.
reconnectReconnectOptions{} (strategy defaults below)Tunes the auto-reconnect backoff strategy.
authGuardAuthGuardOptionson (bounds QR/pairing regeneration)Caps how many QR codes / pairing codes the client regenerates, with an escalating pairing cooldown, so a stuck auth loop can’t spam WhatsApp into a restriction.
operationGuardOperationGuardOptionson (spaces group/community/newsletter ops)Serializes and rate-limits sensitive group / community / newsletter operations per category so rapid bulk actions don’t trip a ban.
presencePresenceThrottleOptionson (drops duplicate presence)Drops repeated presence updates (typing / recording / online) for the same chat within a short window.
scheduleRateLimitPerSecnumber1 (0 disables)Max scheduled messages dispatched per second, smoothing out a backlog of overdue jobs so they don’t all fire at once.
autoConnectbooleantrueWhen true, the constructor calls connect() on the next microtask. Set false to connect manually.
qrTerminalbooleantrueWhen true, prints the QR code to the terminal in addition to emitting the qr event.
statusLogbooleantrueWhen true, writes human-readable connection status lines to stderr and suppresses noisy libsignal logs.
cacheSignalbooleantrueWhen true, wraps the auth store with an in-memory signal-key cache for faster reads.
baileysPartial<UserFacingSocketConfig>{}Extra Baileys socket config merged into the internal config (after the internal defaults markOnlineOnConnect: false / syncFullHistory: false / qrTimeout: 60000, before the managed auth / logger).
⚠️

auth and logger are forced internally and override anything you pass through baileys. The safety defaults markOnlineOnConnect: false, syncFullHistory: false, and qrTimeout are set before your baileys spread, so those three you can override. Use the top-level auth / store / logger options rather than reaching into baileys for those concerns.

sessionId

A label for this connection. It has two effects: it becomes the default auth folder (./.zaileys/auth/<sessionId>) when you do not pass a custom auth, and it is included as the sessionId field on every emitted event so you can tell sessions apart in a multi-account process.

import { Client } from 'zaileys' const primary = new Client({ sessionId: 'account-a' }) const secondary = new Client({ sessionId: 'account-b' }) primary.on('connect', ({ sessionId, me }) => console.log(sessionId, 'ready as', me.id)) secondary.on('connect', ({ sessionId, me }) => console.log(sessionId, 'ready as', me.id))

Two clients sharing the same sessionId would also share the same default auth folder and clash. Give each account a unique sessionId.

authType & phoneNumber

authType selects how you log in. With the default 'qr', a QR string is emitted (and printed to the terminal unless qrTerminal is false). With 'pairing', the client requests an 8-digit code for the given phoneNumber and emits it via the pairing-code event.

import { Client } from 'zaileys' const client = new Client({ authType: 'qr' }) client.on('qr', ({ qrString, expiresAt }) => { console.log('Scan this QR:', qrString, 'expires at', new Date(expiresAt)) })
🚫

When authType is 'pairing' and phoneNumber is missing, connect() rejects with phoneNumber is required when authType is "pairing". With autoConnect on (the default), that rejection surfaces through the error event.

commandPrefix

Setting commandPrefix activates the command framework. Pass a single prefix or an array; empty strings are filtered out. When no prefix is configured, client.command(...) handlers never fire (the dispatcher only attaches once there is at least one prefix, at least one registered command, and a live socket).

import { Client, type Middleware } from 'zaileys' const client = new Client({ commandPrefix: ['/', '!'] }) const logging: Middleware = async (ctx, next) => { console.log(`[command] ${ctx.command} from ${ctx.senderId}`) await next() } client.use(logging) client.command('ping', async (ctx) => { await ctx.reply('pong') })

See Commands for spec syntax, args/flags parsing, and middleware.

ignoreMe

With the default true, any inbound message whose sender is your own logged-in account is dropped before reaching text / message handlers — handy so an echo bot does not respond to itself. Set it to false when you intentionally want to react to your own messages (for example an owner-triggered command from your own number).

import { Client } from 'zaileys' // React to messages from your own account too (e.g. owner-only triggers). const client = new Client({ ignoreMe: false }) client.on('text', async (msg) => { if (msg.text === '.ping') await msg.reply('pong') })

auth

The credential/signal-key store. Defaults to a FileAuthStore rooted at ./.zaileys/auth/<sessionId>. Pass any AuthStoreBundle to persist auth elsewhere (SQLite, Postgres, Redis, Convex, or your own implementation). The bundle has two halves:

interface AuthStoreBundle { readonly creds: AuthCredsStore // readCreds / writeCreds / deleteCreds readonly signal: AuthStore // read / write / delete / clear / close }
import { Client, SqliteAuthStore } from 'zaileys' const client = new Client({ sessionId: 'main', auth: new SqliteAuthStore({ database: './zaileys.db' }), })

Unless you disable cacheSignal, the client wraps your auth in an in-memory signal-key cache on first connect. See Storage Adapters for every bundled adapter and its constructor options.

store

The message/chat/contact/presence store, also used to persist scheduled broadcast jobs. Defaults to MemoryMessageStore (lost on restart). Swap in a persistent MessageStore for durability — the store is bound to the socket on connect and powers quoted-message lookups and the scheduler.

import { Client, SqliteMessageStore } from 'zaileys' const client = new Client({ store: new SqliteMessageStore({ database: './zaileys.db' }), })

See Storage Adapters for the full MessageStore interface and adapter options.

logger

A structured logger with debug, info, warn, error, and fatal methods. When omitted, zaileys builds a Pino logger whose level comes from the ZAILEYS_DEBUG environment variable:

ZAILEYS_DEBUGResulting level
unsetsilent
1info
silent / fatal / error / warn / info / debug / tracethat exact level
anything elsesilent

You may pass a partial logger — any missing method is replaced with a no-op, so providing just error and warn is valid.

import { Client } from 'zaileys' const client = new Client({ logger: { debug: () => {}, info: (...a) => console.log('[info]', ...a), warn: (...a) => console.warn('[warn]', ...a), error: (...a) => console.error('[error]', ...a), fatal: (...a) => console.error('[fatal]', ...a), }, })

The quickest way to see internal logs without writing a custom logger is ZAILEYS_DEBUG=1 (or ZAILEYS_DEBUG=debug) in your environment.

citation

Supplies the predicates behind the per-message citation object in message context. Both fields accept either a list of JIDs or an async predicate function.

import { Client } from 'zaileys' const client = new Client({ citation: { authors: ['628xxx@s.whatsapp.net'], // or (jid) => jid.endsWith('@s.whatsapp.net') banned: (jid) => jid.startsWith('62800'), }, }) client.on('text', async (msg) => { if (await msg.citation.banned()) return if (await msg.citation.authors()) await msg.reply('Hello, author!') })

reconnect

Tunes the exponential-backoff reconnect strategy. The default {} uses the values below; override only the fields you care about.

FieldTypeDefaultDescription
enabledbooleantrueWhether to reconnect at all after a non-fatal disconnect.
maxAttemptsnumberInfinityMaximum reconnect attempts before giving up.
initialDelayMsnumber3000Base delay for the first attempt; doubles each subsequent attempt. (Default raised from 1000 — instant reconnect storms are a ban trigger.)
maxDelayMsnumber60000Upper bound on the (jittered) delay between attempts.
jitterFactornumber0.2Randomization applied to the delay (±20% by default) to avoid thundering herds.
rateLimitedDelayMsnumber300000Fixed backoff used when the disconnect reason is rate-limited (HTTP 429), instead of the exponential ladder.
import { Client } from 'zaileys' const client = new Client({ reconnect: { maxAttempts: 10, initialDelayMs: 2000, maxDelayMs: 30000, jitterFactor: 0.3, }, })
⚠️

Fatal disconnects (such as logged-out) never trigger a reconnect regardless of these settings; zaileys clears the auth folder so you can re-authenticate. See Events for the disconnect / reconnecting payloads.

A rate-limited (HTTP 429) disconnect is non-fatal — zaileys still reconnects, but it uses the fixed rateLimitedDelayMs (default 5 minutes) instead of the exponential ladder, so it backs off hard rather than hammering WhatsApp while you are being throttled.

authGuard

authGuard bounds how many times the client regenerates a login QR or requests a pairing code. Every reconnect creates a fresh socket that re-emits a QR / requests a new pairing code; with no cap and no cooldown, an auth flow that never completes turns into a loop that spams WhatsApp — which answers with HTTP 429 (rate-overlimit) and the dreaded “Your account is restricted right now”. The guard puts a ceiling on that.

FieldTypeDefaultDescription
enabledbooleantrueSet { enabled: false } to restore the old unlimited behavior.
maxQrAttemptsnumber5Total QR codes emitted before the client gives up.
maxPairingAttemptsnumber3Total pairing-code requests before giving up.
pairingCooldownMsnumber60000Base cooldown between pairing requests; it escalates per attempt (60s, then 120s, then 180s…) capped at 300000 (5 min).

When the budget is exhausted the client stops (it does not keep looping), emits an auth-exhausted event, and tears down. Calling client.connect() again resets the budget and retries; the budget also resets automatically on a successful connection.

import { Client } from 'zaileys' const client = new Client({ authType: 'pairing', phoneNumber: '628xxxxxxxxxx', authGuard: { maxPairingAttempts: 3, pairingCooldownMs: 60_000, }, }) client.on('auth-exhausted', ({ kind, attempts, max }) => { console.error(`auth gave up after ${attempts}/${max} ${kind} attempts — fix auth, then connect() again`) })
⚠️

Keep authGuard on. It is the main protection against spamming WhatsApp into an account restriction during a stuck QR / pairing loop. Only set { enabled: false } if you have your own external throttling. See Account restricted / banned if you have already hit the limit.

operationGuard

operationGuard serializes and spaces out sensitive group / community / newsletter operations. Rapidly joining or creating groups and mass-adding members is one of the top ban triggers, so each operation category has a minimum interval — a rapid second call to the same category simply waits until the interval elapses.

FieldTypeDefaultDescription
enabledbooleantrueSet { enabled: false } to disable spacing entirely.
intervalsMsPartial<Record<OperationCategory, number>>{}Per-category minimum-interval overrides (ms).

Default minimum interval per category:

CategoryDefault interval (ms)
group.create60000
group.join30000
group.participants10000
group.update3000
community.create120000
community.join30000
community.update3000
newsletter.create120000
newsletter.follow2000
newsletter.update3000

The affected methods are client.group.create / addMember / removeMember / promote / demote / acceptInvite, client.community.create / createGroup / acceptInvite, and client.newsletter.create / follow / unfollow.

import { Client } from 'zaileys' const client = new Client({ operationGuard: { intervalsMs: { 'group.participants': 15_000, // slow member adds down even further }, }, })
⚠️

Mass group joins / creates / member-adds on a fresh number are a fast path to a ban. Leaving operationGuard on (the default) keeps those calls spaced out automatically. Disable with { enabled: false } only if you handle pacing yourself.

presence

presence drops duplicate presence updates. A repeated client.presence.typing(jid) / recording(jid) / online() for the same (type, chat) within minIntervalMs is silently dropped (no socket call); different chats are independent.

FieldTypeDefaultDescription
enabledbooleantrueSet { enabled: false } to send every presence update.
minIntervalMsnumber1000Window within which a repeated presence update for the same chat is dropped.
import { Client } from 'zaileys' const client = new Client({ presence: { minIntervalMs: 1000 }, })

Spamming presence updates (e.g. emitting “typing…” on every keystroke) is needless socket traffic that contributes to looking abusive. Throttling is on by default; disable it with { enabled: false } if you need every update through.

scheduleRateLimitPerSec

Caps how many scheduled messages are dispatched per second (default 1; set 0 to disable). If many overdue scheduled jobs come due at the same instant — for example right after loadPending() replays a backlog — they no longer all fire simultaneously, which would otherwise look like a burst of automated sends.

import { Client } from 'zaileys' const client = new Client({ scheduleRateLimitPerSec: 1, // at most one scheduled message per second })

Smoothing the scheduler’s output spreads a backlog over time instead of blasting it at once. Set scheduleRateLimitPerSec: 0 only if you explicitly want every due job to fire immediately.

autoConnect

With the default true, the constructor schedules connect() on the next microtask, so simply constructing a Client starts connecting. Set it to false to register listeners first (or do setup work) and connect explicitly.

import { Client } from 'zaileys' const client = new Client({ autoConnect: false }) client.on('qr', ({ qrString }) => console.log('Scan:', qrString)) client.on('connect', ({ me }) => console.log('Ready as', me.id)) await client.connect()

Under auto-connect, an early connection failure is reported through the error event (only if you have an error listener). With manual connect, the rejected connect() promise is yours to catch.

qrTerminal

Controls whether the QR code is rendered to the terminal. The qr event still fires either way, so disable this when you render the QR yourself (e.g. in a web UI) and do not want terminal output.

import { Client } from 'zaileys' // Emit the QR event only; do not draw it in the terminal. const client = new Client({ qrTerminal: false }) client.on('qr', ({ qrString }) => renderInBrowser(qrString))

statusLog

When true (default), zaileys writes concise connection status lines (connecting, qr, pairing-code, connected, reconnecting, disconnect) to stderr, and suppresses noisy libsignal log output. Set it to false for completely silent operation when you handle status via events instead.

import { Client } from 'zaileys' const client = new Client({ statusLog: false }) // no stderr status lines

cacheSignal

When true (default), the client wraps your auth store in an in-memory cache for signal keys on the first connect(), reducing repeated reads from disk/DB. Disable it if your adapter already caches or you need every read to hit the backing store.

import { Client } from 'zaileys' const client = new Client({ cacheSignal: false })

baileys

Escape hatch for advanced Baileys socket configuration. Your object is spread into the internal config after the safety defaults markOnlineOnConnect: false, syncFullHistory: false, and qrTimeout: 60000 — so you can override those three — and before the managed auth and logger, which you cannot.

import { Client } from 'zaileys' const client = new Client({ baileys: { browser: ['zaileys', 'Chrome', '1.0.0'], syncFullHistory: false, }, })
⚠️

Do not set auth or logger here — the client forces them and your values are ignored. Use the top-level auth / store / logger options instead. markOnlineOnConnect, syncFullHistory, and qrTimeout are merely internal defaults you can override here if you really need to.

Fully-configured example

A Client exercising the full surface of ClientOptions:

import { Client, SqliteAuthStore, SqliteMessageStore } from 'zaileys' const client = new Client({ sessionId: 'production-bot', authType: 'pairing', phoneNumber: '628xxxxxxxxxx', auth: new SqliteAuthStore({ database: './zaileys.db' }), store: new SqliteMessageStore({ database: './zaileys.db' }), commandPrefix: ['/', '!'], ignoreMe: true, citation: { authors: ['628xxx@s.whatsapp.net'], banned: (jid) => jid.startsWith('62800'), }, reconnect: { maxAttempts: 20, initialDelayMs: 3000, maxDelayMs: 30000, jitterFactor: 0.25, rateLimitedDelayMs: 300_000, }, authGuard: { maxQrAttempts: 5, maxPairingAttempts: 3 }, operationGuard: { enabled: true }, presence: { minIntervalMs: 1000 }, scheduleRateLimitPerSec: 1, autoConnect: true, qrTerminal: false, statusLog: true, cacheSignal: true, logger: { info: (...a) => console.log('[info]', ...a), warn: (...a) => console.warn('[warn]', ...a), error: (...a) => console.error('[error]', ...a), }, baileys: { browser: ['zaileys', 'Chrome', '1.0.0'], }, }) client.on('pairing-code', ({ code }) => console.log('Pairing code:', code)) client.on('connect', ({ me }) => console.log('Connected as', me.id)) client.command('ping', async (ctx) => { await ctx.reply('pong') })

Which options unlock which features

GoalOption(s)
Enable the command routercommandPrefix
Persist auth/messages across restartsauth, storeStorage Adapters
Log in without scanning a QRauthType: 'pairing' + phoneNumber
Render the QR in your own UIqrTerminal: false + listen for the qr event
Run multiple accounts in one processone Client per unique sessionId
Quiet operationstatusLog: false, default silent logger
Connect on your own scheduleautoConnect: false + await client.connect()
Per-message author/ban checkscitationEvents
Avoid WhatsApp spam restriction / banauthGuard, operationGuard, presence (on by default)

See also

Last updated on