
Product
Introducing Repository Access Permissions and Custom Roles
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.
gifted-btns
Advanced tools
bun install gifted-btns
npm install gifted-btns
yarn add gifted-btns
By default, Baileys cannot send interactive buttons. The root cause is that Baileys lacks the required binary node wrappers (biz, interactive, native_flow) that WhatsApp expects for interactive messages.
The enhanced functionality provided by this gifted-btns package provides the missing functionality by:
interactiveButtons format to the proper protobuf structurebiz, interactive, native_flow, bot) via additionalNodesbot node with biz_bot: '1') to enable advanced conversational AI flowsbiz node)const { sendButtons } = require('gifted-btns');
await sendButtons(sock, jid, {
title: 'Header Title', // optional header
text: 'Pick one option below', // body
footer: 'Footer text', // optional footer
image: { url: 'https://example.com/image.jpg' }, // optional image (can be url, buffer, or path)
aimode: true, // optional boolean, defaults to false. Set true to add biz_bot: '1' node for AI features in private chats.
buttons: [
{
id: 'quick_1', text: 'Quick Reply' }, // legacy simple shape auto‑converted
{
name: 'cta_url',
buttonParamsJson: JSON.stringify({
display_text: 'Open Site',
url: 'https://example.com'
})
}
]
});
For full control (multiple advanced button kinds in one message) use sendInteractiveMessage with interactiveButtons directly.
const { sendInteractiveMessage } = require('gifted-btns');
await sendInteractiveMessage(sock, jid, {
text: 'Advanced native flow demo',
footer: 'All the things',
interactiveButtons: [
// Quick reply (explicit form)
{
name: 'quick_reply',
buttonParamsJson: JSON.stringify({ display_text: 'Reply A', id: 'reply_a' })
},
// Single select picker (list inside a button)
{
name: 'single_select',
buttonParamsJson: JSON.stringify({
title: 'Pick One',
sections: [{
title: 'Choices',
rows: [
{ header: 'H', title: 'Hello', description: 'Says hi', id: 'opt_hello' },
{ header: 'B', title: 'Bye', description: 'Says bye', id: 'opt_bye' }
]
}]
})
}
]
});
Below are the most common & observed name values for nativeFlowMessage.buttons[] along with their required JSON keys. You can mix several in one interactiveButtons array (WhatsApp will decide layout).
| Name | Purpose | buttonParamsJson (required keys) |
|---|---|---|
quick_reply | Simple reply that sends its id back | { display_text, id } |
single_select | In‑button picker list | { title, sections:[{ title?, rows:[{ id, title, description?, header? }] }] } |
cta_url | Open URL | { display_text, url, merchant_url? } |
cta_copy | Copy text to clipboard | { display_text, copy_code } |
cta_call | Tap to dial | { display_text, phone_number } |
cta_catalog | Open business catalog | { display_text? } (WA may ignore extra keys) |
send_location | Request user location (special flow) | { display_text? } |
review_and_pay | Order / payment summary (special) | Payment structured payload (server‑validated) |
payment_info | Payment info flow | Payment structured payload |
mpm | Multi product message (catalog) | Vendor internal structure |
wa_payment_transaction_details | Show transaction | Transaction reference keys |
automated_greeting_message_view_catalog | Greeting -> catalog | (Minimal / internal) |
Not all special names are guaranteed to render outside official / business clients; unsupported ones are simply ignored by WhatsApp. Core stable ones for bots are: quick_reply, single_select, cta_url, cta_copy, cta_call.
await sendInteractiveMessage(sock, jid, {
text: 'Contact actions',
interactiveButtons: [
{ name: 'cta_url', buttonParamsJson: JSON.stringify({ display_text: 'Docs', url: 'https://example.com' }) },
{ name: 'cta_copy', buttonParamsJson: JSON.stringify({ display_text: 'Copy Code', copy_code: 'ABC-123' }) },
{ name: 'cta_call', buttonParamsJson: JSON.stringify({ display_text: 'Call Support', phone_number: '+1234567890' }) },
{ name: 'cta_url', buttonParamsJson: JSON.stringify({ display_text: 'Email Us', url: 'mailto:support@example.com?subject=Need%20Help' }) } // Using cta_url for email
]
});
await sendInteractiveMessage(sock, jid, {
text: 'Explore products or reply',
interactiveButtons: [
{ name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Hello', id: 'hi' }) },
{ name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Pricing', id: 'pricing' }) },
{ name: 'cta_catalog', buttonParamsJson: JSON.stringify({}) }
]
});
await sendInteractiveMessage(sock, jid, {
text: 'Please share your location for delivery:',
interactiveButtons: [
{ name: 'send_location', buttonParamsJson: JSON.stringify({ display_text: 'Share Location' }) }
]
});
await sendInteractiveMessage(sock, jid, {
text: 'Choose one item',
interactiveButtons: [
// Single select picker (list inside a button)
{
name: 'single_select',
buttonParamsJson: JSON.stringify({
title: 'Pick One',
sections: [
{
title: 'Choices',
rows: [
{ header: 'H', title: 'Hello', description: 'Says hi', id: 'opt_hello' },
{ header: 'B', title: 'Bye', description: 'Says bye', id: 'opt_bye' }
]
},
{
title: 'Other Options',
rows: [
{ header: 'T', title: 'Testing', description: 'Just a test', id: 'opt_test' },
{ header: 'C', title: 'Cancel', description: 'Nevermind', id: 'opt_cancel' }
]
}
]
})
}
]
});
Tip: Legacy simple objects like
{ id: 'x', text: 'Label' }passed tosendButtonsauto‑convert toquick_reply.
Private chat: adds biz + interactive/native_flow. If aimode: true is passed, it additionally injects the bot node (biz_bot: '1') to enable conversational AI rendering.
Group chat: adds only biz + interactive/native_flow.
When special first button names (review_and_pay, payment_info, mpm, etc.) are detected, version/name attributes change to match official client traffic so WhatsApp enables those flows.
The wrapper detects button types using the same logic as itsukichan:
listMessage → 'list'buttonsMessage → 'buttons'interactiveMessage.nativeFlowMessage → 'native_flow'Authoring (you write):
{ text, footer, interactiveButtons: [{ name, buttonParamsJson }, ...] }
Wrapper builds (sent to WA):
{ interactiveMessage: { nativeFlowMessage: { buttons: [...] }, body:{ text }, footer:{ text } } }
sendInteractiveMessageLow‑level power helper used by all higher level wrappers. Use this when you need to:
quick_reply + single_select + cta_url).interactiveMessage content (after internal transformation) while still benefiting from automatic binary node injection.statusJidList, additionalAttributes, experimental fields) or manually append extra additionalNodes.async function sendInteractiveMessage(sock, jid, content, options = {})
sock: Active Baileys socket (must expose relayMessage, logger, authState or user).jid: Destination WhatsApp JID (user or group). Auto‑detects group via WABinary.isJidGroup.content: High‑level authoring object. Accepts either a regular Baileys message shape or the enhanced authoring shape:
text (string) Body text (mapped to interactiveMessage.body.text).footer (string) Footer (mapped to interactiveMessage.footer.text).title / subtitle (string) Optional header title (mapped to interactiveMessage.header.title).image (Object) Optional header image. Can be a URL ({ url: '...' }), a buffer ({ buffer: <Buffer> }), or a local path.aimode (boolean) Optional flag. Defaults to false. Set to true to inject the bot node (biz_bot: '1') which is often required to trigger conversational AI features and rendering in private chats.interactiveButtons (Array) Array of button descriptors. Each item should be either:
{ name: '<native_flow_name>', buttonParamsJson: JSON.stringify({...}) } (already normalized), or{ id, text } / { buttonId, buttonText: { displayText } } which is auto‑normalized to a quick_reply.contextInfo) pass through unchanged.options: (Optional) Extra relay + generation options:
generateWAMessageFromContent (e.g. custom timestamp).additionalNodes (Array) Prepend your own binary nodes (the function appends required interactive nodes after detection).additionalAttributes (Object) Extra attributes for the root relay stanza.statusJidList, useCachedGroupMetadata (advanced Baileys relay options).convertToInteractiveMessage(content) if interactiveButtons exist, producing:
{ interactiveMessage: { nativeFlowMessage: { buttons: [...] }, header?, body?, footer? } }
generateWAMessageFromContent, normalizeMessageContent, isJidGroup, generateMessageIDV2). Throws if unavailable.WAMessage bypassing normal send validation (lets unsupported interactive types through).getButtonType then derives binary node tree with getButtonArgs.biz node (with nested interactive/native_flow/... for buttons and lists) when interactive.{ tag: 'bot', attrs: { biz_bot: '1' } } automatically for private (1:1) chats enabling rendering of interactive flows.relayMessage with additionalNodes.sock.upsertMessage) for private chats if sock.config.emitOwnEvents is set (groups are skipped to avoid duplicates).Resolves with the full constructed WAMessage object ({ key, message, messageTimestamp, ... }) so you can log/store/await acks exactly like a standard sock.sendMessage call.
Socket is required if sock is null/undefined.WhiskeySockets functions not available if internal modules cannot be loaded (e.g. path changes). In such a case you may fall back to plain sock.sendMessage for non‑interactive messages.sendButtons / sendInteractiveButtonsBasic for simple quick replies + common CTA cases.sendInteractiveMessage for any combination including single_select, special native flow names, or when you need to attach custom nodes.const { sendInteractiveMessage } = require('gifted-btns');
await sendInteractiveMessage(sock, jid, {
text: 'Pick or explore',
footer: 'Advanced demo',
interactiveButtons: [
{ name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Hi', id: 'hi' }) },
{ name: 'cta_url', buttonParamsJson: JSON.stringify({ display_text: 'Docs', url: 'https://example.com' }) },
{ name: 'single_select', buttonParamsJson: JSON.stringify({
title: 'Menu',
sections: [{
title: 'Options',
rows: [
{ id: 'a', title: 'Alpha', description: 'First item' },
{ id: 'b', title: 'Beta', description: 'Second item' }
]
}]
}) }
]
}, {
additionalNodes: [ { tag: 'biz', attrs: { experimental_flag: '1' } } ] // will be merged before auto interactive nodes
});
| First Button Name | Injected Node Variant | Notes |
|---|---|---|
review_and_pay | biz with native_flow_name=order_details | Payment/order style flow |
payment_info | biz with native_flow_name=payment_info | Payment info flow |
mpm, cta_catalog, send_location, call_permission_request, wa_payment_transaction_details, automated_greeting_message_view_catalog | biz > interactive(native_flow v=1) > native_flow(v=2,name=<name>) | Specialized (may require official client) |
| Anything else / mixed | biz > interactive(native_flow v=1) > native_flow(v=9,name=mixed) | Generic path covering standard quick replies, lists, CTAs |
Cost is roughly equivalent to a standard sendMessage call; extra overhead is a small synchronous transformation + node injection. Suitable for high‑volume bots. Consider standard Baileys concurrency limits for large broadcast scenarios.
Interactive send: { type, nodes, private } – remove or redirect if noisy.biz and private chats include the bot node.buttonParamsJson is valid JSON string (catch JSON.stringify mistakes early).buttonParamsJson payloads.sendInteractiveMessage without a socket that includes relayMessage (e.g., passing a partially constructed object).bot node for private chats (not needed; auto added).If you already built a correct interactiveMessage object you can call:
await sendInteractiveMessage(sock, jid, {
interactiveMessage: {
nativeFlowMessage: {
buttons: [ { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Hi', id: 'hi' }) } ]
},
body: { text: 'Direct native flow' }
}
});
You can now send all mainstream interactive button variants (quick replies, URL / copy / call CTAs, single select lists) plus experimental special flows from Baileys exactly like the official client, with automatic handling for groups vs private chats and without editing fork source.
FAQs
This is a plugin to make baileys more interactive(send buttons)
The npm package gifted-btns receives a total of 8,504 weekly downloads. As such, gifted-btns popularity was classified as popular.
We found that gifted-btns demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.

Product
Socket MCP now lets AI assistants review org alerts, investigate threats using the Socket threat feed, and inspect package files in addition to dependency scoring.

Product
Socket Firewall blocks malicious VS Code and Open VSX extensions before install, protecting developers from compromised editor marketplaces.