Media

One rule governs all media in this package: wherever a WAMediaUpload is accepted, you can pass { url }, a Buffer, or a readable stream โ€” the library fetches, encrypts, and uploads it for you.

Sending โ€” per type

TypeContent shapePer-type options that exist
Image{ image: upload, caption?, jpegThumbnail? }width/height are read from the image; thumbnails auto-generated
Video{ video: upload, caption? }gifPlayback: true sends it as a looping GIF-style video; ptv: true sends a round video note
Audio{ audio: upload, mimetype }ptt: true = voice note (waveform + duration computed); set seconds to override duration
Document{ document: upload, mimetype, fileName }caption supported; fileLength optional
Sticker{ sticker: upload }WebP expected (512ร—512 is the platform norm). For whole packs, use StickerPack
Location{ location: { degreesLatitude, degreesLongitude }, name?, address? }Live location uses liveLocation instead
Contact{ contacts: { displayName, contacts: [{ vcard }] } }vCard string per contact
Link preview{ text, ... } with a URL insideSet generateHighQualityLinkPreview: true in the socket config for rich thumbnails, or pass a manual thumbnail/matchedText via extended text options
View onceany media + viewOnce: trueWorks on image/video/document sends
Disappearingany content + option ephemeralExpirationSeconds: 86400 / 604800 / 7776000 โ€” pass in the send options, not the content

Downloading what people send you

import { downloadMediaMessage } from 'aunubaileys'
import fs from 'node:fs'

sock.ev.on('messages.upsert', async ({ messages, type }) => {
  if (type !== 'notify') return
  for (const m of messages) {
    const img = m.message?.imageMessage
    if (!img) continue
    const buffer = await downloadMediaMessage(m, 'buffer', {})
    fs.writeFileSync('./inbox/' + m.key.id + '.jpg', buffer)
  }
})

downloadMediaMessage(message, 'buffer' | 'stream', options) decrypts and returns the media. For big files use 'stream' and pipe it โ€” buffering a 2 GB video into RAM is how bots die.

When a download fails: re-upload requests

Old media can fail to decrypt because the sender's device no longer serves it. The recovery path built into the socket:

// Ask the sender's client to re-upload the media
await sock.requestPlaceholderResend(m.key)

// The answer arrives as an event, not a return value:
sock.ev.on('messages.media-update', (updates) => {
  for (const { key, media, error } of updates) {
    if (media) console.log('Media refreshed for', key.id) // retry the download now
    else console.log('Re-upload failed:', error)
  }
})

Practical notes from the field