Skip to main content
This page grows the bot from Getting started into one that handles commands, media, a poll and a multi-step flow with validation. It runs unchanged in the terminal simulator and on WhatsApp.

The complete bot

Try it

Send /reset in the middle of the flow to leave it: commands always win over an active conversation.

What each part does

1. The bot and its provider

new Bot({ provider }) wires the transport-agnostic engine to a transport. Transport options live on the provider: session: 'my-bot' saves the WhatsApp login in ./sessions/my-bot/, so later starts reconnect without a QR. defineBot and isMainModule let the simulator inject its own provider instead of BaileysProvider (see Terminal simulator). The texts the framework writes for you (slash command replies, “please answer with text”, timeouts) are in English by default. new Bot({ provider, lang: 'es' }) switches them to the bundled Spanish texts; Internationalization covers several languages.

2. Slash commands

slashCommandsPlugin installs /start, /help, /reset, /cancel, /id and /ping. greeting replaces the text of /start and /help, so list your commands in it; without greeting both answer with the built-in command list. Commands win over an active conversation, so /reset or /cancel always escapes a flow.

3. Simple replies

bot.when(trigger, handler) compares the whole message, normalized (lowercase, accents stripped, spaces collapsed): 'hi' matches “hi” and “Hí ”, not “hi there”. A list matches any of its entries. Use { pattern: 'hi', match: 'contains' } for substring matching, or a regex. ctx.pushName is the sender’s display name when the transport reports one. See Special triggers for every trigger form.

4. Media and polls

Every ctx send method takes a URL, a local path or a Buffer:
What each transport supports natively, and how the rest degrades, is in Messages & media and Channels.

5. Flows

flow('register') compiles to a real conversation. .ask(text, key) sends text, waits for the reply and stores it in c.state[key]; validate returns true or the text to send before asking again. .choose() sends a numbered menu and stores the chosen key (the user can answer with the number or the label). The function form of .say((ctx, c) => ...) reads everything captured so far, and .timeout('5m') ends an abandoned conversation. The flow starts when a message matches its trigger (register). More in Conversation flows.

6. Catch-all

bot.onMessage() runs only when nothing else (a command, an active conversation, a when handler or a flow trigger) consumed the message.

7. Start

bot.on('qr') fires when the provider needs you to authenticate (a QR string on Baileys); bot.on('ready') fires once the transport is connected. After await bot.start() the bot can send; before it, sends reject with BotNotRunningError.

Next steps