Skip to content
MessageBuilder
Documentation menu
Product carousel

Product carousel

Build a carousel catalog from a product array, upload media, and add buttons to each card.

Examples
v4.8/Baileys runtime
On this page

Carousels work well for small catalogs: three to seven cards, each with an image and one action.

1. Prepare the data

const products = [
  {
    id: 'arabica-coffee',
    name: 'Arabica coffee 250g',
    price: '$12',
    image: 'https://example.com/coffee.jpg',
  },
  {
    id: 'brew-kit',
    name: 'Manual brew kit',
    price: '$34',
    image: 'https://example.com/kit.jpg',
  },
  {
    id: 'tumbler',
    name: 'Tumbler 500ml',
    price: '$17',
    image: 'https://example.com/tumbler.jpg',
  },
];

2. Upload images and build cards

import { Carousel, Toolkit } from './lib/MessageBuilder.js';

async function buildCards(sock, products) {
  return Promise.all(
    products.map(async (product) => {
      const imageUrl = await Toolkit.resolveMedia(
        sock,
        product.image,
        'image',
        { resolveUrl: true }
      );

      return {
        header: {
          hasMediaAttachment: true,
          imageMessage: { url: imageUrl },
        },
        body: { text: product.name },
        footer: { text: product.price },
        nativeFlowMessage: {
          buttons: [
            {
              name: 'quick_reply',
              buttonParamsJson: JSON.stringify({
                display_text: 'View product',
                id: `product:${product.id}`,
              }),
            },
          ],
        },
      };
    })
  );
}
async function sendCatalog(sock, jid) {
  const cards = await buildCards(sock, products);

  await new Carousel(sock)
    .setBody('Recommended products for you')
    .setFooter('Prices may change')
    .addCard(cards)
    .send(jid);
}

4. Handle the selection

const selectedId =
  msg.message?.buttonsResponseMessage?.selectedButtonId;

if (selectedId?.startsWith('product:')) {
  const productId = selectedId.split(':')[1];
  await sendProductDetail(sock, msg.key.remoteJid, productId);
}

Notes

  • addCard() accepts one object or an array. Header media is required.
  • Toolkit.resolveMedia(..., { resolveUrl: true }) forces a reupload. For stable public CDN URLs, leave it false to save time.
  • Do not reuse one ID for multiple cards if you plan to edit them later. Carousel IDs live at the button level, not the card level.
  • Test card counts on target devices. Too many cards force users to scroll a long way.
  • If an image fails to load, the carousel may render without media. Provide a fallback image or skip that product.

Safe pattern

const cards = await buildCards(sock, products.slice(0, 7));

if (cards.length === 0) {
  await sock.sendMessage(jid, { text: 'The catalog is empty.' });
  return;
}

await new Carousel(sock).setBody('Catalog').addCard(cards).send(jid);