Skip to main content

Documentation Index

Fetch the complete documentation index at: https://zapo.to/llms.txt

Use this file to discover all available pages before exploring further.

Media is sent through the same client.message.send method, using a typed media content object. The builder fills in the protocol-managed fields (encryption keys, SHA-256 digests, direct path, upload) for you — you provide the source and the mimetype.
For usable media, install @zapo-js/media-utils and wire a processor through the media client option. Media still uploads without it, but without a processor it has no thumbnail/preview, dimensions, or waveform — so it may arrive as a plain attachment.

Media input

The media field accepts several input types:
type MediaInput = Uint8Array | ArrayBuffer | Readable | string
Prefer a file path (string) or a Readable stream over a Buffer/Uint8Array. zapo streams media through the pipeline without buffering the whole file in memory — passing a path or stream keeps memory flat regardless of file size. Reading a large file into a Buffer first defeats that and is discouraged. (Buffer is also avoided internally in favor of Uint8Array.)

Images

// Preferred — pass a file path; zapo streams it
await client.message.send(jid, {
  type: 'image',
  media: './photo.jpg',
  mimetype: 'image/jpeg',
  caption: 'A photo'
})

// Or a Readable stream (e.g. from an HTTP response)
import { createReadStream } from 'node:fs'

await client.message.send(jid, {
  type: 'image',
  media: createReadStream('./photo.jpg'),
  mimetype: 'image/jpeg'
})

Video

await client.message.send(jid, {
  type: 'video',
  media: './clip.mp4',
  mimetype: 'video/mp4',
  caption: 'A clip',
  gifPlayback: false
})
For a round push-to-video (PTV) message, use type: 'ptv' with the same shape.

Audio & voice notes

// Regular audio
await client.message.send(jid, {
  type: 'audio',
  media: './song.mp3',
  mimetype: 'audio/mpeg'
})

// Voice note (push-to-talk)
await client.message.send(jid, {
  type: 'audio',
  media: './voice.ogg',
  mimetype: 'audio/ogg; codecs=opus',
  ptt: true
})
Voice notes render best as Opus in an OGG container. Enable media processing to auto-generate waveforms and normalize voice notes.

Documents

await client.message.send(jid, {
  type: 'document',
  media: './report.pdf',
  mimetype: 'application/pdf',
  fileName: 'Q3 Report.pdf',
  caption: 'The quarterly report'
})

Stickers

await client.message.send(jid, {
  type: 'sticker',
  media: await readFile('./sticker.webp'),
  mimetype: 'image/webp'
})
For a full sticker pack, use type: 'sticker-pack' with stickers, a trayIcon, and pack metadata (stickerPackId, name, publisher).

View-once

Wrap image/video/audio as view-once with the send option:
await client.message.send(jid, {
  type: 'image',
  media: './secret.jpg',
  mimetype: 'image/jpeg'
}, {
  viewOnce: true
})

Downloading incoming media

The message coordinator decrypts and downloads media from an incoming event. Three flavors are available — prefer the streaming ones:
client.on('message', async (event) => {
  if (!event.message?.imageMessage) return

  // Preferred — stream to a file (constant memory)
  await client.message.downloadToFile(event, './incoming.jpg')

  // Or consume the Readable stream yourself
  const stream = await client.message.download(event)

  // Avoid for large media — buffers the entire file in memory
  const bytes = await client.message.downloadBytes(event)
})
download() / downloadToFile() stream the media and keep memory flat regardless of size. downloadBytes() materializes the whole file in memory — reach for it only on small media, and cap it with maxBytes.
All three accept either a WaIncomingMessageEvent or a raw Proto.IMessage, plus optional WaDownloadMediaOptions (for example maxBytes to cap downloadBytes).

Media processing

For proper media, use a media processor. Install @zapo-js/media-utils and pass one through the media client option — it probes and processes media (dimensions, duration, thumbnails, waveforms, voice-note normalization) before upload. Without it, media still uploads but lacks this processing:
import { createMediaProcessor } from '@zapo-js/media-utils'

const client = new WaClient({
  store,
  sessionId: 'default',
  media: {
    processor: createMediaProcessor(),
    generateThumbnail: true,
    generateWaveform: true,
    normalizeVoiceNote: true
  }
}, logger)
@zapo-js/media-utils shells out to ffmpeg/ffprobe and uses sharp. Make sure those binaries are available in your environment.
Last modified on May 27, 2026