Skip to content
MessageBuilder
Documentation menu
Nested menu list

Nested menu list

Build a cafe menu with a header image, multiple sections, and response handling.

Examples
v4.8/Baileys runtime
On this page

Scenario: a cafe bot that shows a categorized menu. The user picks a row, and the bot replies with product details.

1. Send the menu

import { Button } from './lib/MessageBuilder.js';

async function sendMenu(sock, jid) {
  return new Button(sock)
    .setTitle('Nusantara Cafe')
    .setImage('https://example.com/menu-banner.jpg', {
      caption: 'This season’s menu',
    })
    .setBody('Pick a category, then tap the item you want to order.')
    .setFooter('Open daily, 8:00 AM – 10:00 PM')
    .addSelection('Open menu')
    .makeSection('Food')
    .makeRow('Village-style fried rice', '$8', 'Spice level adjustable', 'fried-rice')
    .makeRow('Chicken noodles with meatballs', '$7', 'Large portion', 'chicken-noodles')
    .makeSection('Drinks')
    .makeRow('Iced palm sugar latte', '$6', 'House blend', 'iced-latte')
    .makeRow('Matcha latte', '$7', 'Oat milk', 'matcha')
    .send(jid);
}

2. Handle the selection

sock.ev.on('messages.upsert', async ({ messages, type }) => {
  if (type !== 'notify') return;

  for (const msg of messages) {
    if (msg.key.fromMe) continue;

    const selectedId =
      msg.message?.listResponseMessage?.singleSelectReply?.selectedRowId;

    if (selectedId === 'fried-rice') {
      await sendProductDetail(sock, msg.key.remoteJid, {
        name: 'Village-style fried rice',
        price: '$8',
        description: 'Fried rice with egg, chicken, and vegetables. Spice level 0–5.',
        id: 'fried-rice',
      });
    }
  }
});

3. Product detail with a CTA

async function sendProductDetail(sock, jid, product) {
  await new Button(sock)
    .setImage(`https://example.com/${product.id}.jpg`)
    .setBody(`${product.name}\n${product.price}\n\n${product.description}`)
    .setFooter('Tap a button to add it to your order')
    .addReply('Add to order', `add:${product.id}`)
    .addUrl('View on web', `https://example.com/menu/${product.id}`, true)
    .send(jid);
}

Notes

  • WhatsApp limits how many rows render comfortably. Split large menus into sections or separate messages.
  • The header argument of makeRow() is optional and is usually left empty.
  • Row IDs must be unique and stable. Do not use array indexes that may change.
  • If you call loadFrom(), the last selection and section are restored. Verify with console.dir before editing.

Variation: no image

Remove setImage(). The body becomes more compact, but keep readability on small screens in mind.

new Button(sock)
  .setBody('Choose from the menu:')
  .addSelection('Open menu')
  .makeSection('Favorites')
  .makeRow('Iced latte', '$6', '', 'iced-latte')
  .send(jid);