Skip to content
MessageBuilder
Documentation menu
Media resolution

Media resolution

How MessageBuilder handles URLs, buffers, base64, resizing, uploads, and video metadata.

Concepts
v4.8/Baileys runtime
On this page

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:

  1. downloads the video buffer;
  2. calculates the file size;
  3. reads the duration with Toolkit.getMp4Duration();
  4. 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.