malvin-btns
v1.2.0
Published
This is a plugin to make baileys more interactive
Readme
malvin-btns
A powerful, production-ready interactive button plugin for the Baileys WhatsApp library.
Send interactive buttons, quick replies, URL buttons, copy buttons, call buttons, and single-select menus — with optional image headers — without touching Baileys core files.
The Problem
By default, WhiskeySockets/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 Solution
malvin-btns fills the gap by:
- Detecting button messages using WhatsApp's expected format
- Converting simple button definitions to the correct protobuf structure
- Injecting missing binary nodes (
biz,interactive,native_flow,bot) viaadditionalNodes - Automatically handling private vs. group chat requirements
- Processing images - automatically downloads, encrypts, and uploads images to WhatsApp's CDN
No core file edits. No monkey-patching. Just drop it in and go.
Features
| Feature | Status |
|---|---|
| No modifications to Baileys core | ✅ |
| Automatic binary node injection | ✅ |
| Private chat support (bot node with biz_bot: '1') | ✅ |
| Group chat support (biz node only) | ✅ |
| Quick reply buttons | ✅ |
| URL, Copy, Call CTA buttons | ✅ |
| Single-select menus | ✅ |
| Send location button | ✅ |
| Image header support (auto-upload & encryption) | ✅ |
| Input validation with detailed errors | ✅ |
| Works with multiple Baileys forks | ✅ |
| Regular messages pass through unchanged | ✅ |
Installation
npm install malvin-btnsYou also need a Baileys package:
# pick one
npm install mrxd-baileys
npm install @whiskeysockets/baileysQuick Start
const { makeWASocket } = require('mrxd-baileys');
const { sendButtons } = require('malvin-btns');
const sock = makeWASocket({ /* your config */ });
await sendButtons(sock, jid, {
title: 'Hello there!',
text: 'Pick an option below',
footer: 'Powered by malvin-btns',
buttons: [
{ id: 'help', text: '🆘 Help' },
{ id: 'about', text: 'ℹ️ About' },
{
name: 'cta_url',
buttonParamsJson: JSON.stringify({
display_text: '🌐 Visit Website',
url: 'https://host.malvintech.sbs'
})
}
]
});Image Header Support
Add an image above your buttons by passing an image object. The image is automatically downloaded, encrypted, and uploaded to WhatsApp's CDN — you just provide the URL.
await sendButtons(sock, jid, {
title: 'Product Showcase',
text: 'Check out our latest collection',
image: { url: 'https://example.com/product.jpg' },
footer: 'Limited time offer',
buttons: [
{ id: 'shop', text: '🛒 Shop Now' },
{ id: 'details', text: '📋 Details' }
]
});You can also use a local file path or buffer:
// Local file
image: { path: '/path/to/image.jpg' }
// Buffer
image: { buffer: bufferData }Handling Button Replies
When a user taps a button, WhatsApp sends back the button's id as a regular message:
sock.ev.on('messages.upsert', async ({ messages }) => {
const msg = messages[0];
const text =
msg.message?.conversation ||
msg.message?.extendedTextMessage?.text || '';
if (text === 'help') {
await sock.sendMessage(msg.key.remoteJid, { text: 'How can I help you?' });
}
if (text === 'about') {
await sock.sendMessage(msg.key.remoteJid, { text: 'Made with malvin-btns 🚀' });
}
});Button Types
| Name | Purpose | Required buttonParamsJson 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 a 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? } |
| send_location | Request user location | { display_text? } |
Advanced Usage
For full control over all button types in one message, use sendInteractiveMessage:
const { sendInteractiveMessage } = require('malvin-btns');
await sendInteractiveMessage(sock, jid, {
text: 'Advanced button demo',
footer: 'malvin-btns',
image: { url: 'https://example.com/header.jpg' },
interactiveButtons: [
{
name: 'quick_reply',
buttonParamsJson: JSON.stringify({ display_text: 'Reply A', id: 'reply_a' })
},
{
name: 'single_select',
buttonParamsJson: JSON.stringify({
title: 'Pick One',
sections: [{
title: 'Options',
rows: [
{ id: 'opt_hello', title: 'Hello', description: 'Say hi' },
{ id: 'opt_bye', title: 'Bye', description: 'Say bye' }
]
}]
})
}
]
});URL + Copy + Call in One Message
await sendInteractiveMessage(sock, jid, {
text: 'Contact actions',
interactiveButtons: [
{
name: 'cta_url',
buttonParamsJson: JSON.stringify({ display_text: '🌐 Docs', url: 'https://host.malvintech.sbs/doc' })
},
{
name: 'cta_copy',
buttonParamsJson: JSON.stringify({ display_text: '📋 Copy Code', copy_code: 'MALVIN-2025' })
},
{
name: 'cta_call',
buttonParamsJson: JSON.stringify({ display_text: '📞 Call Support', phone_number: '+1234567890' })
}
]
});Single-Select Menu
await sendInteractiveMessage(sock, jid, {
text: 'Choose a plan',
interactiveButtons: [
{
name: 'single_select',
buttonParamsJson: JSON.stringify({
title: 'Plans',
sections: [{
title: 'Available Plans',
rows: [
{ id: 'plan_free', title: 'Free', description: '4,000 requests/day' },
{ id: 'plan_pro', title: 'Premium', description: 'Unlimited — one-time payment' }
]
}]
})
}
]
});Error Handling
const { sendButtons, InteractiveValidationError } = require('malvin-btns');
try {
await sendButtons(sock, jid, { text: 'Hi', buttons: [] });
} catch (err) {
if (err instanceof InteractiveValidationError) {
console.error(err.formatDetailed());
}
}How It Works
What You Write
{
text: 'Hello',
footer: 'Footer',
image: { url: 'https://example.com/img.jpg' },
interactiveButtons: [{ name, buttonParamsJson }, ...]
}What Gets Sent to WhatsApp
{
interactiveMessage: {
header: {
hasMediaAttachment: true,
imageMessage: { url: 'https://mmg.whatsapp.net/...', ... }
},
nativeFlowMessage: { buttons: [...] },
body: { text: 'Hello' },
footer: { text: 'Footer' }
}
}Binary Nodes Injected
| Chat Type | Nodes Added |
|---|---|
| Private | biz + interactive/native_flow + bot (biz_bot: '1') |
| Group | biz + interactive/native_flow only |
API Reference
sendButtons(sock, jid, data, options?)
Simplified sending for quick replies and basic CTAs.
| Parameter | Type | Description |
|---|---|---|
| sock | WASocket | Baileys socket instance |
| jid | string | Recipient JID |
| data.text | string | Message body (required) |
| data.title | string | Header title (optional) |
| data.footer | string | Footer text (optional) |
| data.image | object | Image header - { url, path, or buffer } (optional) |
| data.buttons | array | Button objects |
sendInteractiveMessage(sock, jid, content, options?)
Full control over all button types and structures.
| Parameter | Type | Description |
|---|---|---|
| sock | WASocket | Baileys socket instance |
| jid | string | Recipient JID |
| content.text | string | Message body (required) |
| content.footer | string | Footer text (optional) |
| content.image | object | Image header - { url, path, or buffer } (optional) |
| content.interactiveButtons | array | Full button definitions |
Compatibility
| Baileys Package | Compatible | |---|---| | mrxd-baileys | ✅ Yes | | @whiskeysockets/baileys | ✅ Yes (7.0.0-rc.2+) | | baileys | ✅ Yes | | @adiwajshing/baileys | ✅ Yes |
Requirements
- Node.js v20 or higher
- Baileys 7.0.0-rc.2+
Author
mrxdking — @XdKing2
- 📺 YouTube / TikTok / Instagram: @malvintech
- 💬 Telegram: @malvintech
License
ISC © Malvin Tech
