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, optionally, a mimetype.
Mimetype resolution
mimetype is optional. The builder resolves it in this order:
- The
mimetypeyou pass on the content object wins. - If a
WaMediaProcessorwithdetectMimetypeis configured, the builder calls it (sniffing magic bytes).@zapo-js/media-utilsimplements this on top offile-type^19 — installfile-typeto enable detection. - Otherwise the builder throws for
image/video/audio/document/ptvmessages.
image/webp when no mimetype is set. Readable stream inputs with no mimetype are staged to a temp file before detection runs.
Media input
Themedia field accepts several input types:
Images
Video
type: 'ptv' with the same shape.
Audio & voice notes
Documents
Stickers
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:Downloading incoming media
The message coordinator decrypts and downloads media from an incoming event. Three flavors are available — prefer the streaming ones:WaIncomingMessageEvent or a raw Proto.IMessage, plus optional WaDownloadMediaOptions (for example maxBytes to cap downloadBytes).
Without a connected client
downloadMediaMessage is a free function that mirrors client.message.download but does not need a paired session. The encrypted-media metadata travels inside the (already decrypted) message itself, so you can re-download media from a persisted event long after the original socket is gone — useful for offline workers, archive replays, or anything that processes stored messages without spinning up a WaClient.
WaIncomingMessageEvent or a raw Proto.IMessage, returns a Readable you own (pipe it or .destroy() it — an unconsumed stream leaks the socket). MAC + SHA-256 verification runs as bytes are consumed, same semantics as the coordinator method. Throws when the message has no downloadable media.
For lower-level access — when you want to do the CDN fetch yourself, hand the keys to another process, or just inspect what’s downloadable —
resolveMediaPayload returns the keys + hashes without doing any I/O:
null when the message has no downloadable media, or when the proto carried no directPath / mediaKey. It unwraps ephemeralMessage, viewOnceMessage / viewOnceMessageV2, and documentWithCaptionMessage before resolving. Supported kinds: image, video (gif when gifPlayback), audio (ptt when ptt), document, sticker, ptv.
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:
@zapo-js/media-utils shells out to ffmpeg/ffprobe and uses sharp. Make sure those binaries are available in your environment.