Skip to main content

Overview

WAPI provides simple methods for working with media messages. You can send various media types and download incoming media files using the Context class methods.

Send Images

Share photos with captions and options

Send Videos

Send video files with GIF playback support

Send Audio

Share voice messages and audio files

Send Stickers

Send custom WebP stickers

Sending Images

The replyWithImage() method allows you to send images from URLs or Buffer objects:
bot.command("photo", async (ctx) => {
  const imageUrl = "https://example.com/photo.jpg";
  
  await ctx.replyWithImage(imageUrl, {
    caption: "Here's your photo!"
  });
});
The replyWithImage() method (from context.ts:41-68) accepts either a URL string or a Buffer. When using URLs, WhatsApp downloads the image directly. The default mimetype is image/jpeg.

Image Options

Available options for replyWithImage():
OptionTypeDescription
captionstringText caption for the image
mimetypestringMIME type (default: image/jpeg)
viewOncebooleanMake it a view-once message
mentionsstring[]JIDs to mention in caption

Sending Videos

The replyWithVideo() method works similarly to images but supports video files:
bot.command("video", async (ctx) => {
  const videoUrl = "https://example.com/video.mp4";
  
  await ctx.replyWithVideo(videoUrl, {
    caption: "Check out this video!"
  });
});
Use the gifPlayback: true option to make videos play as GIFs in WhatsApp without sound and with automatic looping.

Video Options

Available options for replyWithVideo():
OptionTypeDescription
captionstringText caption for the video
mimetypestringMIME type (default: video/mp4)
viewOncebooleanMake it a view-once message
gifPlaybackbooleanDisplay as GIF (no sound, loops)
mentionsstring[]JIDs to mention in caption

Sending Audio

The replyWithAudio() method sends audio files and voice messages:
bot.command("audio", async (ctx) => {
  const audioUrl = "https://example.com/song.mp3";
  
  await ctx.replyWithAudio(audioUrl);
});
The replyWithAudio() method (from context.ts:99-126) defaults to audio/mpeg mimetype. For voice messages, WhatsApp automatically detects the audio waveform.

Audio Options

Available options for replyWithAudio():
OptionTypeDescription
mimetypestringMIME type (default: audio/mpeg)
viewOncebooleanMake it a view-once message
mentionsstring[]JIDs to mention

Sending Stickers

The replyWithSticker() method sends WebP stickers:
import fs from "fs";

bot.command("sticker", async (ctx) => {
  // Stickers must be in WebP format as a Buffer
  const stickerBuffer = fs.readFileSync("./stickers/cool.webp");
  
  await ctx.replyWithSticker(stickerBuffer);
});
The replyWithSticker() method (from context.ts:128-146) only accepts Buffer objects, not URLs. Stickers must be in WebP format (image/webp mimetype).

Creating Stickers from Images

You can convert images to stickers using image processing libraries:
import sharp from "sharp";

bot.command("makesticker", async (ctx) => {
  // Check if message has an image
  if (ctx.type !== "imageMessage") {
    await ctx.reply("Please send an image with this command.");
    return;
  }
  
  try {
    // Download the image
    const imageBuffer = await ctx.download();
    
    // Convert to WebP sticker format (512x512)
    const stickerBuffer = await sharp(imageBuffer)
      .resize(512, 512, {
        fit: "contain",
        background: { r: 0, g: 0, b: 0, alpha: 0 }
      })
      .webp()
      .toBuffer();
    
    // Send as sticker
    await ctx.replyWithSticker(stickerBuffer);
  } catch (error) {
    await ctx.reply("Failed to create sticker.");
  }
});

Downloading Media

The download() method retrieves media content from incoming messages:
bot.use(async (ctx, next) => {
  // Check if message contains media
  const mediaTypes = [
    "imageMessage",
    "videoMessage",
    "audioMessage",
    "documentMessage",
    "stickerMessage"
  ];
  
  if (mediaTypes.includes(ctx.type)) {
    console.log(`Received ${ctx.type}`);
    console.log(`Size: ${ctx.size} bytes`);
    console.log(`Mimetype: ${ctx.mimetype}`);
    
    // Download the media
    const buffer = await ctx.download();
    console.log(`Downloaded ${buffer.length} bytes`);
    
    // Process the buffer (save to file, upload to storage, etc.)
  }
  
  await next();
});
The download() method (from context.ts:174-176) uses Baileys’ downloadMediaMessage function to retrieve media content as a Buffer.

Practical Examples

1

Image Mirror Bot

Download an image and send it back:
bot.command("mirror", async (ctx) => {
  if (ctx.type !== "imageMessage") {
    await ctx.reply("Please send an image with this command.");
    return;
  }
  
  // Download the image
  const imageBuffer = await ctx.download();
  
  // Send it back
  await ctx.replyWithImage(imageBuffer, {
    caption: "Here's your image back!"
  });
});
2

Media Information Bot

Display details about received media:
bot.command("mediainfo", async (ctx) => {
  const mediaTypes = ["imageMessage", "videoMessage", "audioMessage"];
  
  if (!mediaTypes.includes(ctx.type)) {
    await ctx.reply("Please send media with this command.");
    return;
  }
  
  const info = [
    `*Media Information*`,
    `Type: ${ctx.type.replace("Message", "")}`,
    `Size: ${(ctx.size / 1024).toFixed(2)} KB`,
    `Mimetype: ${ctx.mimetype}`,
    `Hash: ${ctx.hash}`,
    ctx.text ? `Caption: ${ctx.text}` : "No caption"
  ].join("\n");
  
  await ctx.reply(info);
});
3

Auto-Save Media

Automatically save received media to disk:
import fs from "fs";
import path from "path";

bot.use(async (ctx, next) => {
  const mediaTypes = ["imageMessage", "videoMessage", "audioMessage"];
  
  if (mediaTypes.includes(ctx.type)) {
    try {
      const buffer = await ctx.download();
      
      // Create filename from hash and mimetype
      const ext = ctx.mimetype.split("/")[1];
      const filename = `${ctx.hash}.${ext}`;
      const filepath = path.join("./downloads", filename);
      
      // Save to disk
      fs.writeFileSync(filepath, buffer);
      console.log(`Saved media to ${filepath}`);
    } catch (error) {
      console.error("Failed to save media:", error);
    }
  }
  
  await next();
});
4

Gallery Command

Send multiple images in sequence:
bot.command("gallery", async (ctx) => {
  const images = [
    "https://example.com/photo1.jpg",
    "https://example.com/photo2.jpg",
    "https://example.com/photo3.jpg"
  ];
  
  await ctx.reply("Sending gallery...");
  
  for (let i = 0; i < images.length; i++) {
    await ctx.replyWithImage(images[i], {
      caption: `Photo ${i + 1} of ${images.length}`
    });
  }
});

Media Type Detection

You can create a utility to check media types:
function isMediaMessage(type: string): boolean {
  const mediaTypes = [
    "imageMessage",
    "videoMessage",
    "audioMessage",
    "documentMessage",
    "stickerMessage"
  ];
  return mediaTypes.includes(type);
}

bot.use(async (ctx, next) => {
  if (isMediaMessage(ctx.type)) {
    console.log("Media message received");
  }
  await next();
});

Media Flow Diagram

Best Practices

Always wrap download() calls in try-catch blocks:
bot.command("download", async (ctx) => {
  try {
    const buffer = await ctx.download();
    console.log(`Downloaded ${buffer.length} bytes`);
  } catch (error) {
    await ctx.reply("Failed to download media.");
    console.error(error);
  }
});
Check media size before processing:
bot.use(async (ctx, next) => {
  if (ctx.type === "imageMessage") {
    const maxSize = 10 * 1024 * 1024; // 10 MB
    
    if (ctx.size > maxSize) {
      await ctx.reply("Image is too large (max 10 MB).");
      return;
    }
    
    const buffer = await ctx.download();
    // Process image...
  }
  await next();
});
Specify correct mimetypes for better compatibility:
// Images
await ctx.replyWithImage(buffer, { mimetype: "image/jpeg" });
await ctx.replyWithImage(buffer, { mimetype: "image/png" });

// Videos
await ctx.replyWithVideo(buffer, { mimetype: "video/mp4" });

// Audio
await ctx.replyWithAudio(buffer, { mimetype: "audio/mpeg" });
await ctx.replyWithAudio(buffer, { mimetype: "audio/ogg; codecs=opus" });
Remove temporary media files after processing:
import fs from "fs";
import os from "os";
import path from "path";

bot.command("process", async (ctx) => {
  if (ctx.type !== "imageMessage") return;
  
  const buffer = await ctx.download();
  const tempFile = path.join(os.tmpdir(), `${ctx.id}.jpg`);
  
  try {
    fs.writeFileSync(tempFile, buffer);
    // Process the file...
  } finally {
    // Clean up
    if (fs.existsSync(tempFile)) {
      fs.unlinkSync(tempFile);
    }
  }
});
Compress images and videos to reduce bandwidth:
import sharp from "sharp";

bot.command("optimized", async (ctx) => {
  const originalUrl = "https://example.com/large-image.jpg";
  
  // Download and optimize
  const response = await fetch(originalUrl);
  const buffer = Buffer.from(await response.arrayBuffer());
  
  const optimized = await sharp(buffer)
    .resize(1920, 1080, { fit: "inside" })
    .jpeg({ quality: 80 })
    .toBuffer();
  
  await ctx.replyWithImage(optimized, {
    caption: "Optimized image"
  });
});

Error Handling

Common errors and how to handle them:
bot.use(async (ctx, next) => {
  if (ctx.type === "imageMessage") {
    try {
      const buffer = await ctx.download();
      
      // Process the image
      await processImage(buffer);
      
      await ctx.reply("Image processed successfully!");
    } catch (error) {
      if (error.message.includes("connection")) {
        await ctx.reply("Network error. Please try again.");
      } else if (error.message.includes("size")) {
        await ctx.reply("File is too large.");
      } else {
        await ctx.reply("Failed to process image.");
      }
      
      console.error("Media error:", error);
    }
  }
  
  await next();
});

Next Steps

Groups

Work with group chats and manage group media

Advanced Features

Explore advanced bot patterns and utilities

Handling Messages

Learn about message types and parsing

API Reference

Full API documentation for Context methods

Build docs developers (and LLMs) love