WhatsApp supports multiple interactive button types: native buttons, template buttons, list select, and AI-style rich response markdown.
Native WA buttons appear below your message. Supports header image, title, body text, and footer. Maximum 3 buttons.
| Field | Type | Description |
|---|---|---|
text | string | Body text (minimum a space if not used) |
buttons | Array | Array of button objects. Max 3 |
image / video | WAMediaUpload | Optional media header |
title | string | Text header (used when no media) |
footer | string | Footer text |
| Field | Description |
|---|---|
buttonId | Unique button ID for detection |
buttonText.displayText | Button label text |
type | 1 = reply (standard) |
await sock.sendMessage(jid, { text: 'Choose an option:', title: 'Main Menu', footer: 'Select one', buttons: [ { buttonId: 'products', buttonText: { displayText: 'Products' }, type: 1 }, { buttonId: 'help', buttonText: { displayText: 'Help' }, type: 1 }, { buttonId: 'about', buttonText: { displayText: 'About' }, type: 1 }, ], }) // Button with image header await sock.sendMessage(jid, { text: 'Special promotion!', image: { url: './banner.jpg' }, buttons: [ { buttonId: 'buy', buttonText: { displayText: 'Buy Now' }, type: 1 }, ], })
When a user clicks a button, WA sends a buttonsResponseMessage. Listen on messages.upsert:
sock.ev.on('messages.upsert', ({ messages }) => { for (const msg of messages) { const btn = msg.message?.buttonsResponseMessage if (btn) { console.log('User selected:', btn.selectedButtonId, btn.selectedDisplayText) } } })
Three types: URL (opens in browser), Quick Reply (sends reply), Call (dials number).
| Field | Description |
|---|---|
text | Main body text |
templateButtons | Array of buttons. Can mix all three types |
footer | Footer text (optional) |
| Type | Structure | Description |
|---|---|---|
| URL | { urlButton: { displayText, url } } | Opens URL in browser with confirmation |
| Quick Reply | { quickReplyButton: { displayText, id } } | Sends automatic reply with ID |
| Call | { callButton: { displayText, phoneNumber } } | Dials phone number |
await sock.sendMessage(jid, { text: 'Need help?', templateButtons: [ { urlButton: { displayText: 'Visit Site', url: 'https://example.com' } }, { quickReplyButton: { displayText: 'Yes please', id: 'need_help' } }, { callButton: { displayText: 'Call Support', phoneNumber: '+6281234567890' } }, ], })
Quick Reply responses come through the same buttonsResponseMessage as native buttons:
sock.ev.on('messages.upsert', ({ messages }) => { for (const msg of messages) { if (msg.message?.buttonsResponseMessage) { const { selectedButtonId, selectedDisplayText } = msg.message.buttonsResponseMessage console.log(`User clicked "${selectedDisplayText}" (${selectedButtonId})`) } } })
Dropdown menu with sections and rows. Ideal for multi-level menus or catalogs.
| Field | Type | Description |
|---|---|---|
text | string | Main body text |
buttonText | string | Dropdown button label |
title | string | Title (optional) |
footer | string | Footer (optional) |
sections | Section[] | Array of categories with title & rows |
| Field | Description |
|---|---|
id | Unique row ID for detection |
title | Row title |
description | Row description (optional) |
await sock.sendMessage(jid, { text: 'Select menu:', buttonText: 'View Menu', title: 'Menu Items', footer: 'Select one', sections: [ { title: 'Drinks', rows: [ { id: 'coffee', title: 'Coffee', description: 'Black coffee' }, { id: 'tea', title: 'Tea', description: 'Sweet tea' }, { id: 'juice', title: 'Orange Juice', description: 'Fresh squeezed' }, ], }, { title: 'Food', rows: [ { id: 'rice', title: 'Fried Rice', description: 'Special fried rice' }, { id: 'noodles', title: 'Chicken Noodles', description: 'Complete chicken noodles' }, ], }, ], })
sock.ev.on('messages.upsert', ({ messages }) => { for (const msg of messages) { const list = msg.message?.listResponseMessage if (list) { const selectedId = list.singleSelectReply?.selectedRowId const selectedTitle = list.singleSelectReply?.selectedRowTitle console.log(`Selected: ${selectedTitle} (${selectedId})`) } } })
All button types above (native, template, list select) are internally converted to native_flow messages by WhatsApp. You can handle responses via buttonsResponseMessage, listResponseMessage, or interactiveResponseMessage.
sock.ev.on('messages.upsert', ({ messages }) => { for (const msg of messages) { const interactive = msg.message?.interactiveResponseMessage if (interactive) { const { nativeFlowResponseMessage } = interactive console.log('Response:', nativeFlowResponseMessage) } } })
Converts markdown into an interactive AI-style message (similar to ChatGPT on WhatsApp). Add rich: true and write markdown in text.
| Feature | Syntax | Result |
|---|---|---|
| Heading | # Title to #### | Bold header text |
| Bold | *text* | Bold |
| Italic | _text_ | Italic |
| Strikethrough | ~text~ | Strikethrough |
| Code Block | ```javascript ... ``` | Syntax-highlighted code |
| Table | | A | B | with separator | Formatted table |
| Image |  | Inline image |
| Blockquote | > text | Quoted text |
| Horizontal Rule | --- / *** / ___ | Divider |
| Suggest Prompts | :::suggest ... ::: | Suggested action pills |
const md = [ '# Bot Dashboard', '', 'Status: *Online* _24/7_ ~maintenance~', '', '```javascript', "console.log('Ready')", '```', '', '| Metric | Value |', '|--------|-------|', '| Users | 1.234 |', '', '', '', ':::suggest', 'Check Status | Restart | Help', ':::', ].join('\n') await sock.sendMessage(jid, { text: md, rich: true })
Note: Rich response only works with text + rich: true. Cannot be combined with media or buttons. Useful for AI bots and interactive dashboards.
Ask user to share their phone number via a native button. When tapped, WA sends the user's number back to the bot.
await sock.sendMessage(jid, { requestPhoneNumber: true })
sock.ev.on('messages.upsert', ({ messages }) => { for (const msg of messages) { if (msg.message?.requestPhoneNumberMessage) { console.log('User phone:', msg.key.participant || msg.key.remoteJid) } } })