How to Send Text, Media, Location, Contact, and Poll Messages with Baileys

Use sock.sendMessage(jid, content, options?) with a content object matching the AnyMessageContent interface—Baileys automatically handles protocol encoding, encryption, and delivery for text, images, video, audio, documents, location, contacts, and polls.

Baileys provides a unified API to send various message types through WhatsApp Web without manually constructing protobuf payloads. The library's sendMessage method in src/Socket/messages-send.ts orchestrates message generation via generateWAMessage() ([src/Utils/messages.ts#L666-L672]), content normalization in generateWAMessageContent() ([src/Utils/messages.ts#L395-L795]), and encrypted delivery through relayMessage() ([src/Socket/messages-send.ts#L16-L31]). This guide covers the canonical content shapes for each message type with runnable TypeScript examples.

Sending Text Messages with Baileys

The simplest message type requires only a text property. Baileys detects this path at if (hasNonNullishProperty(message, 'text')) ([src/Utils/messages.ts#L996-L1005]).

await sock.sendMessage('1234567890@s.whatsapp.net', { 
  text: 'Hello from Baileys!' 
})

Baileys automatically fetches link previews for URLs in your text. Control this behavior:

await sock.sendMessage(jid, { 
  text: 'Check out https://github.com/WhiskeySockets/Baileys' 
  // linkPreview: false // disable if needed
})

The link preview logic is implemented in src/Utils/link-preview.ts.

Sending Media Messages (Image, Video, Audio, Document, Sticker)

Media messages use the AnyMediaMessageContent union type ([src/Types/Message.ts#L74-L104]). Baileys branches on media type at lines L1026-L1065 in generateWAMessageContent().

Media Type Required Field Key Optional Fields
Image image: WAMediaUpload caption, jpegThumbnail, width, height
Video video: WAMediaUpload caption, gifPlayback, ptv (picture-in-video), width, height
Audio audio: WAMediaUpload ptt (push-to-talk), seconds
Document document: WAMediaUpload, mimetype fileName, caption
Sticker sticker: WAMediaUpload isAnimated, width, height

Image Example

await sock.sendMessage(jid, {
  image: { url: './photo.jpg' },  // WAMediaUpload: URL, Buffer, or Stream
  caption: 'Check this out!'
})

Document with Custom Filename

await sock.sendMessage(jid, {
  document: { buffer: pdfBuffer },
  mimetype: 'application/pdf',
  fileName: 'report-2024.pdf',
  caption: 'Monthly report'
})

Voice Note (Push-to-Talk)

await sock.sendMessage(jid, {
  audio: { url: './voice.ogg' },
  ptt: true,  // displays as waveform, not playable audio
  seconds: 15
})

WAMediaUpload accepts URLs, Buffers, or Streams—Baileys handles upload to WhatsApp's CDN internally.

Sending Location Messages with Baileys

Location messages use the WALocationMessage interface, processed at else if (hasNonNullishProperty(message, 'location')) ([src/Utils/messages.ts#L1038-L1040]).

await sock.sendMessage(jid, {
  location: {
    degreesLatitude: 37.7749,
    degreesLongitude: -122.4194,
    name: 'San Francisco',
    address: 'Golden Gate Bridge'
  }
})

The name and address fields are optional but recommended—they appear in the WhatsApp location card header.

Sending Contact Cards (vCard)

Contact messages wrap vCard data in the contacts structure, handled at lines L1042-L1052 ([src/Utils/messages.ts]).

await sock.sendMessage(jid, {
  contacts: {
    displayName: 'Business Contacts',  // optional section header
    contacts: [
      {
        displayName: 'John Doe',
        vcard: `BEGIN:VCARD
VERSION:3.0
FN:John Doe
ORG:Acme Inc.
TEL;type=CELL;type=VOICE;waid=1234567890:1234567890
EMAIL:john@example.com
END:VCARD`
      }
    ]
  }
})

Multiple contacts can be included in the contacts.contacts array. The waid parameter in the vCard TEL field should match the WhatsApp ID for proper linking.

Creating and Sending Polls

Polls use PollMessageOptions ([src/Types/Message.ts#L136-L144]), processed at else if (hasNonNullishProperty(message, 'poll')) ([src/Utils/messages.ts#L1077-L1085]).

await sock.sendMessage(jid, {
  poll: {
    name: 'Pick a fruit',
    selectableCount: 1,           // 0 = unlimited choices, 1+ = limit
    values: ['🍎 Apple', '🍊 Orange', '🍌 Banana'],
    messageSecret: crypto.randomBytes(32)  // optional: encrypts selections
  }
})

Baileys validates selectableCount against the values array size at lines L1079-L1084. Set selectableCount: 0 for unlimited selections, or >1 for multiple-choice limits.

Complete Working Example

This script demonstrates all five message types sequentially. Replace authentication with your preferred Baileys auth method.

import makeWASocket, { useMultiFileAuthState } from '@whiskeysockets/baileys'
import { Boom } from '@hapi/boom'
import { readFileSync } from 'fs'
import crypto from 'crypto'

async function main() {
  const { state, saveCreds } = await useMultiFileAuthState('./baileys-auth')
  const sock = makeWASocket({
    auth: state,
    printQRInTerminal: true
  })

  sock.ev.on('creds.update', saveCreds)
  sock.ev.on('connection.update', ({ connection }) => {
    if (connection === 'open') console.log('Connected!')
  })

  const jid = '1234567890@s.whatsapp.net'

  // 1️⃣ Text message
  await sock.sendMessage(jid, { text: 'Hello from Baileys!' })

  // 2️⃣ Image with caption
  await sock.sendMessage(jid, {
    image: { url: './assets/demo.jpg' },
    caption: 'Photo demonstration'
  })

  // 3️⃣ Location pin
  await sock.sendMessage(jid, {
    location: {
      degreesLatitude: 40.7128,
      degreesLongitude: -74.0060,
      name: 'New York City',
      address: 'Manhattan, NY 10001'
    }
  })

  // 4️⃣ Contact card
  await sock.sendMessage(jid, {
    contacts: {
      contacts: [{
        displayName: 'Support Team',
        vcard: readFileSync('./assets/contact.vcf', 'utf-8')
      }]
    }
  })

  // 5️⃣ Interactive poll
  await sock.sendMessage(jid, {
    poll: {
      name: 'Preferred stack?',
      selectableCount: 1,
      values: ['MERN', 'PERN', 'LAMP', 'Django/DRF'],
      messageSecret: crypto.randomBytes(32)
    }
  })
}

main().catch(err => {
  if (err instanceof Boom) {
    console.error('WhatsApp error:', err.output.statusCode, err.message)
  } else {
    console.error(err)
  }
})

Key Implementation Details from Source Code

Understanding Baileys' internal flow helps debug issues:

  • Message generation: generateWAMessage() → generateWAMessageContent() builds protocol buffers
  • Type detection: Uses hasNonNullishProperty() checks to branch on content keys (text, image, video, etc.)
  • Encryption: relayMessage() in src/Socket/messages-send.ts handles multi-device encryption and delivery
  • Type safety: All content shapes are defined in src/Types/Message.ts—reference this for exact interfaces

Summary

  • Text: { text: string } with optional linkPreview control
  • Media: Use image, video, audio, document, or sticker keys with WAMediaUpload values
  • Location: { location: { degreesLatitude, degreesLongitude, name?, address? } }
  • Contact: { contacts: { contacts: [{ displayName, vcard }] } } with valid vCard 3.0 strings
  • Poll: { poll: { name, selectableCount?, values, messageSecret? } } for interactive voting

All message types route through sock.sendMessage()—Baileys abstracts protocol details, encryption, and device synchronization.

Frequently Asked Questions

What is the maximum size for media files in Baileys?

Baileys does not enforce size limits—WhatsApp's servers do. Typical limits are 16 MB for documents, 64 MB for most media in regular chats. The library streams large files automatically via WAMediaUpload stream support.

How do I send a message to a group instead of an individual?

Use the group's JID format: 1234567890@g.us instead of 1234567890@s.whatsapp.net. The sendMessage API remains identical; only the JID changes.

Can I combine multiple content types in one message?

No—WhatsApp's protocol enforces single-message-type payloads. To send text with media, use the caption field on media messages rather than a separate text property.

Why does my poll not show secret ballot results?

Include messageSecret: crypto.randomBytes(32) in your poll object. Without this, poll selections are visible to the message sender. The secret enables end-to-end encryption of votes per PollMessageOptions in src/Types/Message.ts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →