All message types are sent through a single function: sendMessage(jid, content, options). Plus manual relay via relayMessage for advanced use cases.
| Parameter | Type | Description |
|---|---|---|
jid | string | Target JID. E.g., 628xxx@s.whatsapp.net, xxx@g.us |
content | AnyMessageContent | Message content object. Structure varies by message type |
options | MiscMessageGenerationOptions | Additional configuration: quoted, ephemeral, etc. |
| Option | Type | Description |
|---|---|---|
quoted | WAMessage | Message to reply to. Pass the full WAMessage object from events |
ephemeralExpiration | number | string | Disappearing message duration: 86400 (24h), 604800 (7d), 7776000 (90d) |
timestamp | Date | Override message timestamp |
mediaUploadTimeoutMs | number | Timeout for media upload to WA server |
statusJidList | string[] | JID list for status broadcast |
backgroundColor | string | Status background color |
font | number | Status font |
broadcast | boolean | Broadcast message |
useCachedGroupMetadata | boolean | Use cached group metadata (default true) |
messageId | string | Override message ID with a custom value |
sendMessage returns Promise<WAMessage>. The returned object has a .key (MessageKey) that can be used for edit, delete, reply:
const sent = await sock.sendMessage(jid, { text: 'Hello' }) console.log(sent.key.id) // message ID console.log(sent.key) // { id, remoteJid, fromMe }
When emitOwnEvents: true (default), sent messages also appear in the messages.upsert event — so you can handle your own messages through event listeners.
Basic text messages. Use text for content, mentions to mention people, mentionAll to mention everyone.
| Field | Type | Required | Description |
|---|---|---|---|
text | string | Yes | Message text content |
mentions | string[] | No | JIDs to mention |
mentionAll | boolean | No | Mention all group participants |
contextInfo | proto.IContextInfo | No | Custom context info |
linkPreview | WAUrlInfo | null | No | Override link preview. Null = disable |
await sock.sendMessage('6281234567890@s.whatsapp.net', { text: 'Hello from bot!' }) // With mention await sock.sendMessage('123@g.us', { text: 'Hello @6281234567890!', mentions: ['6281234567890@s.whatsapp.net'] }) // Mention all await sock.sendMessage('123@g.us', { text: 'Hello everyone!', mentionAll: true }) // Reply / quoted await sock.sendMessage(jid, { text: 'Reply' }, { quoted: originalMsg }) // Disappearing message await sock.sendMessage(jid, { text: 'Disappears in 24h' }, { ephemeralExpiration: 86400 }) // View once await sock.sendMessage(jid, { text: 'View once text', viewOnce: true }) // Edit sent message await sock.sendMessage(jid, { text: 'Edit me', edit: messageKey })
originalMsg is the full WAMessage object from the messages.upsert event. Store it in memory or database, then pass it to quoted.
image accepts Buffer, URL ({ url: string }), or Readable stream.
| Field | Type | Required | Description |
|---|---|---|---|
image | WAMediaUpload | Yes | Buffer / { url: string } / Readable stream |
caption | string | No | Image caption |
mentions | string[] | No | Mentions in caption |
jpegThumbnail | string / Buffer | No | Custom thumbnail (Base64 string or Buffer) |
width / height | number | No | Image dimensions |
contextInfo | proto.IContextInfo | No | Context info |
viewOnce | boolean | No | View once |
albumParentKey | WAMessageKey | No | Album parent key (if part of an album) |
await sock.sendMessage(jid, { image: { url: 'https://example.com/photo.jpg' }, caption: 'Nice' }) await sock.sendMessage(jid, { image: fs.readFileSync('./photo.jpg'), caption: 'Local' }) await sock.sendMessage(jid, { image: { url: './photo.jpg' }, viewOnce: true })
ptv: true = video note (circle). gifPlayback: true = animated GIF.
| Field | Description |
|---|---|
video | Buffer / URL / stream |
caption | Caption |
gifPlayback | true = send as GIF |
ptv | true = video note (circle) |
jpegThumbnail | Custom thumbnail |
width / height | Dimensions |
mentions | Mentions in caption |
await sock.sendMessage(jid, { video: { url: './video.mp4' }, caption: 'Video' }) await sock.sendMessage(jid, { video: { url: './video.mp4' }, ptv: true }) await sock.sendMessage(jid, { video: { url: './video.gif' }, gifPlayback: true })
ptt: true = voice note. seconds can specify duration (optional).
| Field | Description |
|---|---|
audio | Buffer / URL / stream |
ptt | true = voice note |
seconds | Audio duration in seconds |
mimetype | E.g., audio/ogg; codecs=opus, audio/mp4, audio/aac |
// Voice note await sock.sendMessage(jid, { audio: { url: './voice.ogg' }, mimetype: 'audio/ogg; codecs=opus', ptt: true }) // Regular audio await sock.sendMessage(jid, { audio: { url: './song.mp3' }, mimetype: 'audio/mp4' })
Send any file type. mimetype must be correct.
| Field | Description |
|---|---|
document | Buffer / URL / stream |
fileName | Display file name |
mimetype | Required. E.g., application/pdf |
caption | Caption |
contextInfo | Context info |
await sock.sendMessage(jid, { document: { url: './file.pdf' }, fileName: 'document.pdf', mimetype: 'application/pdf', caption: 'Here is the PDF', })
Send a sticker. isAnimated for animated stickers (webp with multiple frames).
| Field | Description |
|---|---|
sticker | Buffer / URL (webp) |
isAnimated | true for animated sticker |
width / height | Optional dimensions |
await sock.sendMessage(jid, { sticker: fs.readFileSync('./sticker.webp') }) await sock.sendMessage(jid, { sticker: { url: './animated.webp' }, isAnimated: true })
Create a poll with options. selectableCount: 1 = single select, >1 = multi-select. values: array of choices.
| Field | Type | Description |
|---|---|---|
name | string | Poll question |
values | string[] | Poll options |
selectableCount | number | 1 = single select, >1 = multi-select |
messageSecret | Uint8Array | 32-byte secret for vote encryption |
toAnnouncementGroup | boolean | For announcement groups |
await sock.sendMessage(jid, { poll: { name: 'Favorite framework?', values: ['Next.js', 'React', 'Vue'], selectableCount: 1 } }) await sock.sendMessage(jid, { poll: { name: 'Skills?', values: ['TS', 'Python', 'Go'], selectableCount: 2 } })
Send a map location. Uses proto.Message.ILocationMessage.
| Field | Description |
|---|---|
degreesLatitude | Latitude (-6.2) |
degreesLongitude | Longitude (106.8) |
name | Location name (optional) |
address | Address (optional) |
comment | Comment (optional) |
await sock.sendMessage(jid, { location: { degreesLatitude: -6.2, degreesLongitude: 106.8, name: 'Jakarta' } })
Send a contact as vCard.
| Field | Description |
|---|---|
contacts.displayName | Display name |
contacts.contacts | Array of { vcard: string } |
const vcard = [ 'BEGIN:VCARD', 'VERSION:3.0', 'FN:Contact Name', 'TEL;waid=628xxx:+6281234567890', 'END:VCARD', ].join('\n') await sock.sendMessage(jid, { contacts: { displayName: 'Contact Name', contacts: [{ vcard }] } })
Add or remove emoji reactions on existing messages. react.text = emoji, empty string removes the reaction.
Must use sendMessage — not a separate function.
// Add reaction await sock.sendMessage(jid, { react: { text: '🔥', key: messageKey } }) // Remove reaction await sock.sendMessage(jid, { react: { text: '', key: messageKey } })
Edit the text of an already-sent message. Pass the target MessageKey via edit. New text via text or caption.
Can only edit your own messages (fromMe: true). Time limit: not fixed, but usually within 24 hours.
// Edit text await sock.sendMessage(jid, { text: 'Updated text', edit: messageKey }) // Edit media caption await sock.sendMessage(jid, { image: { url: 'photo.jpg' }, caption: 'Updated caption', edit: messageKey })
messageKey comes from sent.key if sent by yourself, or from msg.key in the messages.upsert event.
Delete a message. delete takes a MessageKey.
delete: messageKey onlyremoteJid in messageKey, and fromMe: falseedit = '7' for self-delete, edit = '8' for admin delete// Delete own message await sock.sendMessage(jid, { delete: messageKey }) // Admin delete others' message in group await sock.sendMessage(jid, { delete: { id: msgKey.id, remoteJid: jid, fromMe: false, participant: msgKey.participant } })
Forward a message from one chat to another. forward = the original WAMessage object to forward. force: true removes the "Forwarded" label.
// Normal forward (with "Forwarded" label) await sock.sendMessage(targetJid, { forward: originalMessage }) // Force forward (without label) await sock.sendMessage(targetJid, { forward: originalMessage, force: true })
Add viewOnce: true to any message type. Recipient can only view once.
await sock.sendMessage(jid, { image: { url: './secret.jpg' }, viewOnce: true }) await sock.sendMessage(jid, { video: { url: './video.mp4' }, viewOnce: true }) await sock.sendMessage(jid, { text: 'View once text', viewOnce: true }) await sock.sendMessage(jid, { audio: { url: './voice.ogg' }, viewOnce: true, ptt: true })
Two modes: set disappearing for the entire chat, or send a single disappearing message.
Use disappearingMessagesInChat. Type boolean | number:
true = 7 days (WA default)86400 = 24 hours604800 = 7 days7776000 = 90 days0 = offOnly works in groups. For personal chats use updateDefaultDisappearingMode.
// Set 24h in group await sock.sendMessage(jid, { disappearingMessagesInChat: 86400 }) // Disable await sock.sendMessage(jid, { disappearingMessagesInChat: 0 })
Use ephemeralExpiration option (3rd parameter):
await sock.sendMessage(jid, { text: 'Secret' }, { ephemeralExpiration: 86400 }) await sock.sendMessage(jid, { image: { url: './photo.jpg' } }, { ephemeralExpiration: 604800 })
Send multiple media items grouped as one album. The first image/video becomes the cover.
album: { expectedImageCount, expectedVideoCount } — this becomes the parent albumalbumParentKey from the first send result (= sent.key)albumParentKey on each mediaconst album = await sock.sendMessage(jid, { image: { url: './1.jpg' }, caption: 'Album', album: { expectedImageCount: 3 }, }) await sock.sendMessage(jid, { image: { url: './2.jpg' }, albumParentKey: album.key }) await sock.sendMessage(jid, { image: { url: './3.jpg' }, albumParentKey: album.key })
Create an event invitation with date, description, location, and auto-generated RSVP buttons from WA.
| Field | Type | Description |
|---|---|---|
name | string | Event name |
description | string | Event description |
startDate | Date | Start date/time |
endDate | Date | End date/time (optional) |
location | WALocationMessage | Location (lat, lng, name) |
call | 'audio' | 'video' | Add call link button |
isCancelled | boolean | Mark event as cancelled |
isScheduleCall | boolean | Schedule a call |
extraGuestsAllowed | boolean | Allow extra guests |
await sock.sendMessage(jid, { event: { name: 'Community Meetup', description: 'Tech discussion', startDate: new Date('2025-07-01T10:00:00+07:00'), endDate: new Date('2025-07-01T12:00:00+07:00'), location: { degreesLatitude: -6.2, degreesLongitude: 106.8, name: 'Jakarta' }, call: 'video', } })
Pin a message in chat. Requires the target message's MessageKey.
| Field | Type | Description |
|---|---|---|
pin | WAMessageKey | Message key of the message to pin |
type | proto.PinInChat.Type | 1 = PINNED, 2 = UNPINNED |
time | 86400 | 604800 | 2592000 | Pin duration: 24h / 7d / 30d |
await sock.sendMessage(jid, { pin: messageKey, time: 604800 }) // Pin 7 days await sock.sendMessage(jid, { pin: messageKey, type: 2 }) // Unpin
Send images/videos in HD quality. Just add isHd: true.
// HD Image await sock.sendMessage(jid, { image: { url: './photo.jpg' }, caption: 'HD photo!', isHd: true, }) // HD Video await sock.sendMessage(jid, { video: { url: './video.mp4' }, caption: 'HD video!', isHd: true, })
isHd tells WhatsApp to show an "HD" badge on the message. The uploaded file stays at original quality. Works on WhatsApp clients with HD media support (Android/Web). iOS may display as a regular image.
Prevent a message from disappearing. Useful for saving important messages in chats with disappearing mode enabled.
// Keep message (prevent deletion) await sock.sendMessage(jid, { keepInChat: messageKey, type: 1, // KEEP_FOR_ALL }) // Undo keep (restore disappearing) await sock.sendMessage(jid, { keepInChat: messageKey, type: 2, // UNDO_KEEP_FOR_ALL })
Send a product message from a business catalog.
| Field | Description |
|---|---|
product.productImage | Product image (Buffer/URL) |
product.title | Product title |
product.description | Product description |
product.currencyCode | Currency code (IDR) |
product.priceAmount | Price in cents (10000 = $100) |
product.retailerId | Product SKU |
businessOwnerJid | Business owner JID |
body | Additional message |
footer | Footer |
await sock.sendMessage(jid, { product: { productImage: { url: './product.jpg' }, title: 'Premium T-Shirt', description: 'Comfortable cotton', currencyCode: 'IDR', priceAmount: 5000000, retailerId: 'SKU001', }, businessOwnerJid: '628xxx@s.whatsapp.net', body: 'Check out this product!', footer: 'Our Store', })
Send a group invitation.
await sock.sendMessage(jid, { groupInvite: { inviteCode: 'abc123', inviteExpiration: 86400, text: 'Join our group!', jid: '123456@g.us', subject: 'Awesome Group', } })
Share your own phone number in a chat.
await sock.sendMessage(jid, { sharePhoneNumber: true })
Limit message forwarding/sharing.
await sock.sendMessage(jid, { limitSharing: true })
Send a status/story update — the same kind that appears at the top of WhatsApp chats. Send to status@broadcast and optionally control who can see it via statusJidList.
await sock.sendMessage('status@broadcast', { text: 'Hello world! 🌍', }, { statusJidList: [ '628xxx@s.whatsapp.net', '628yyy@s.whatsapp.net', ], backgroundColor: '#4a9eff', font: 1, })
await sock.sendMessage('status@broadcast', { image: { url: './status.jpg' }, caption: 'Trying new features!', }, { statusJidList: ['628xxx@s.whatsapp.net'], })
| Param | Type | Description |
|---|---|---|
| statusJidList | string[] | JIDs allowed to view. If omitted, all contacts can see based on privacy settings |
| backgroundColor | string | Background color (hex). E.g., '#4a9eff' |
| font | number | Font type. 0-3 (WhatsApp supported fonts) |
status@broadcast is different from regular broadcasts. statusJidList is optional — if omitted, the privacy setting determines viewers. If provided, only those JIDs can see the status.
For more control (e.g., adding custom nodes, setting participant manually), use relayMessage directly:
| Param | Type | Description |
|---|---|---|
jid | string | Target |
message | proto.IMessage | Pre-generated protobuf message |
options | MessageRelayOptions | Additional relay config |
import { generateWAMessage, proto } from '@mataram/wa' const msg = await generateWAMessage(jid, { text: 'Manual' }, { userJid: sock.user?.id }) await sock.relayMessage(jid, msg.message!, { messageId: msg.key.id })