Media is the most common cause of failed messages. MessageBuilder normalizes it through Toolkit.resolveMedia() and Toolkit.toUrl().
Accepted input
| Input | Handling |
|---|---|
*.whatsapp.net URL |
Used directly, unless resolveWAUrl: true. |
| Regular URL | Used directly, unless resolveUrl: true. |
| Base64 string | Converted to a Buffer. |
Buffer |
Uploaded to WhatsApp servers when the result must be a url. |
| Array | Processed in parallel with Promise.all; the result stays an array. |
const url = await Toolkit.resolveMedia(sock, buffer, 'image');
const urls = await Toolkit.resolveMedia(sock, [
'https://example.com/a.jpg',
'https://example.com/b.jpg',
], 'image');
resolveMedia options
await Toolkit.resolveMedia(sock, media, 'image', {
resolveUrl: true, // force fetch + reupload for regular URLs
resolveWAUrl: false, // use whatsapp.net URLs as-is
result: 'url', // 'url' | 'buffer' | 'base64'
resize: false, // only applies when result is not 'url'
width: 300,
height: 300,
});
Decision flow:
array? → process each item in parallel
whatsapp.net URL? → use directly (unless resolveWAUrl)
regular URL? → use directly (unless resolveUrl)
base64? → Buffer
buffer? → upload (unless result is buffer/base64)
When should I use resolveUrl?
Use resolveUrl: true when the source URL:
- sits behind authentication or hotlink protection;
- is a signed URL that expires quickly;
- cannot be reached by WhatsApp servers;
- produces 403/404 responses when rendered.
await new Button(sock)
.setImage('https://cdn.internal/banner.jpg')
.setBody('New menu')
.addUrl('View', 'https://example.com')
.send(jid);
setImage() itself does not force a reupload. For full control, upload first:
const imageUrl = await Toolkit.toUrl(sock, buffer, 'image');
button.setImage(imageUrl);
Resizing
Toolkit.resize() uses sharp and always outputs a transparent PNG:
const thumb = await Toolkit.resize(buffer, 300, 300, 'cover');
fit options: cover, contain, fill, inside, outside. For message thumbnails, cover is usually safest.
Video
addVideo() can fill metadata automatically:
await rich.addVideo('https://example.com/demo.mp4', {
autoFill: true,
width: 640,
height: 360,
});
With autoFill: true, the library:
- downloads the video buffer;
- calculates the file size;
- reads the duration with
Toolkit.getMp4Duration(); - extracts a frame with
Toolkit.getMp4Preview().
If you already know the metadata, pass an object to avoid the download:
await rich.addVideo({
url: 'https://example.com/demo.mp4',
mime_type: 'video/mp4',
file_length: 2400000,
duration: 18,
thumbnail: thumbnailBuffer,
});
Cost and latency
- Every reupload adds time and bandwidth.
- Stable public URLs should be used directly.
getMp4Preview()runs ffmpeg; limit video duration and file size.- Cache upload results when media is reused.
Note: WhatsApp CDN URLs can expire. For messages sent much later, reupload the media instead of storing an old URL.