npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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.

npm version License: ISC Node.js GitHub

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:

  1. Detecting button messages using WhatsApp's expected format
  2. Converting simple button definitions to the correct protobuf structure
  3. Injecting missing binary nodes (biz, interactive, native_flow, bot) via additionalNodes
  4. Automatically handling private vs. group chat requirements
  5. 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-btns

You also need a Baileys package:

# pick one
npm install mrxd-baileys
npm install @whiskeysockets/baileys

Quick 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


License

ISC © Malvin Tech