Skip to main content

Create Your First Bot

Let’s build a fully functional WhatsApp bot with basic commands. This guide will take you from zero to a working bot.
1

Set up your project

Create a new directory and initialize your project:
mkdir my-whatsapp-bot
cd my-whatsapp-bot
npm init -y
Update your package.json to use ESM modules:
package.json
{
  "name": "my-whatsapp-bot",
  "type": "module",
  "version": "1.0.0",
  "description": "My WhatsApp bot built with WAPI",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  }
}
2

Install dependencies

Install WAPI and the QR code library:
npm install @imjxsx/wapi qrcode @imjxsx/logger
3

Create your bot file

Create an index.js file with the following code:
index.js
import QRCode from "qrcode";
import { Bot, LocalAuth } from "@imjxsx/wapi";
import Logger from "@imjxsx/logger";

// Create a logger instance
const logger = new Logger({
  level: "INFO",
});

// Generate a unique UUID for your bot
const uuid = "1f1332f4-7c2a-4b88-b4ca-bd56d07ed713";

// Set up local authentication (sessions stored in ./sessions directory)
const auth = new LocalAuth(uuid, "sessions");

// Configure account (empty values for QR code login)
const account = {
  jid: "",
  pn: "",
  name: "",
};

// Create the bot instance
const bot = new Bot(uuid, auth, account, logger);

// Handle QR code event
bot.on("qr", async (qr) => {
  qr = await QRCode.toString(qr, { type: "terminal", small: true });
  console.log(qr);
});

// Handle successful connection
bot.on("open", (account) => {
  bot.logger.info(`Successful login to @${account.name} (${account.pn})`);
});

// Handle disconnection
bot.on("close", (reason) => {
  bot.logger.warn(reason);
});

// Handle errors
bot.on("error", (err) => {
  bot.logger.error(err);
});

// Add a middleware to log all messages
bot.use(async (ctx, next) => {
  bot.logger.info(`New message from '${ctx.from.name}' in '${ctx.chat.name}'`);
  await next();
});

// Add a ping command
bot.command("ping", async (ctx) => {
  await ctx.reply(`> ¡Pong! \`\`\`${bot.ping.toFixed(2)} ms\`\`\``);
});

// Login using QR code
await bot.login("qr");
Replace the UUID with your own unique identifier. You can generate one at uuidgenerator.net.
4

Run your bot

Start your bot:
npm start
You should see a QR code appear in your terminal. Scan it with WhatsApp:
  1. Open WhatsApp on your phone
  2. Go to SettingsLinked Devices
  3. Tap Link a Device
  4. Scan the QR code
Once authenticated, session data is saved locally. You won’t need to scan the QR code again unless you delete the sessions folder.
5

Test your bot

Send a message to your bot:
/ping
Or with the alternative prefix:
!ping
Your bot should reply with the latency:
> ¡Pong! ```XX.XX ms```

Login Methods

WAPI supports two authentication methods:
The simplest method - just scan a QR code with WhatsApp:
const account = {
  jid: "",
  pn: "",
  name: "",
};

const bot = new Bot(uuid, auth, account);

bot.on("qr", async (qr) => {
  qr = await QRCode.toString(qr, { type: "terminal", small: true });
  console.log(qr);
});

await bot.login("qr");
QR code login is perfect for development and testing. The session is saved and reused automatically.

Adding More Commands

Let’s add more functionality to your bot:
index.js
// Echo command - repeats what you say
bot.command("say", async (ctx) => {
  await ctx.reply(ctx.args.join(" "));
});

// Info command - shows message details
bot.command("info", async (ctx) => {
  const info = `
📱 *Chat Info*
• Type: ${ctx.chat.type}
• Name: ${ctx.chat.name}

👤 *Sender Info*
• Name: ${ctx.from.name}
• Phone: ${ctx.from.pn}

📨 *Message Info*
• Has media: ${ctx.hasMedia ? 'Yes' : 'No'}
• Has quoted: ${ctx.hasQuoted ? 'Yes' : 'No'}
  `.trim();

  await ctx.reply(info);
});

// Help command - lists all commands
bot.command("help", async (ctx) => {
  const help = `
🤖 *Available Commands*

/ping - Check bot latency
/say <text> - Echo your message
/info - Show message details
/help - Show this help message
  `.trim();

  await ctx.reply(help);
});

Working with Media

WAPI provides convenient methods for sending images, videos, and audio:
// Send an image with caption
bot.command("image", async (ctx) => {
  await ctx.replyWithImage("https://example.com/image.jpg", {
    caption: "Here's your image!"
  });
});

// Send a video
bot.command("video", async (ctx) => {
  await ctx.replyWithVideo("https://example.com/video.mp4", {
    caption: "Check out this video"
  });
});

// Send an audio file
bot.command("audio", async (ctx) => {
  await ctx.replyWithAudio("https://example.com/audio.mp3");
});

Advanced Example: TikTok Downloader

Here’s a real-world example from the WAPI source code that downloads TikTok videos:
bot.command("tiktok", async (ctx) => {
  const links = ctx.links.filter((v) => 
    /https?:\/\/(www|vt|vm|t)?\.?tiktok\.com\/\S+/.test(v)
  );
  
  if (!links.length) {
    await ctx.reply("> No valid TikTok link was detected in the message.");
    return;
  }
  
  await ctx.reply(`> Processing ${links.length} TikTok links.`);
  
  for (const link of links) {
    const response = await fetch(`https://www.tikwm.com/api?url=${link}`);
    const { data } = await response.json();
    
    if (!data.play && !data.images?.length) {
      await ctx.reply(`> Could not retrieve link information '${link}'`);
      continue;
    }
    
    if (data.images?.length) {
      for (let index = 0; index < data.images.length; index++) {
        const imageUrl = data.images[index];
        if (!index) {
          await ctx.replyWithImage(imageUrl, { 
            caption: `> *Title:* ${data.title}` 
          });
        } else {
          await ctx.replyWithImage(imageUrl);
        }
      }
    } else {
      await ctx.replyWithVideo(data.play, { 
        caption: `> *Title:* ${data.title}` 
      });
    }
  }
});
The context object automatically parses links from messages using ctx.links, making it easy to build download bots and link processors.

Understanding Middleware

Middleware functions run before command handlers and can modify the context or stop execution:
// Log all messages
bot.use(async (ctx, next) => {
  bot.logger.info(`Message from ${ctx.from.name}`);
  await next(); // Continue to next middleware/command
});

// Only allow messages from groups
bot.use(async (ctx, next) => {
  if (ctx.chat.type !== "group") {
    await ctx.reply("This bot only works in groups!");
    return; // Stop execution
  }
  await next();
});

// Add a custom property to context
bot.use(async (ctx, next) => {
  ctx.timestamp = Date.now();
  await next();
});

Context API Reference

The ctx object provides access to message data and utility methods:

Message Properties

ctx.text          // Message text content
ctx.args          // Command arguments as array
ctx.links         // Extracted URLs from message
ctx.hasMedia      // Boolean - message has media
ctx.hasQuoted     // Boolean - message quotes another
ctx.isGroup       // Boolean - message is from group

Sender & Chat Information

ctx.from.name     // Sender's name
ctx.from.pn       // Sender's phone number
ctx.from.jid      // Sender's JID

ctx.chat.name     // Chat name (contact or group)
ctx.chat.type     // "private" or "group"
ctx.chat.jid      // Chat JID

Reply Methods

await ctx.reply("text")                          // Send text
await ctx.replyWithImage(url, { caption })      // Send image
await ctx.replyWithVideo(url, { caption })      // Send video
await ctx.replyWithAudio(url)                    // Send audio

Event Handling

Listen to bot lifecycle events:
bot.on("qr", async (qr) => {
  // Handle QR code for authentication
});

bot.on("otp", (otp) => {
  // Handle OTP code for authentication
});

bot.on("open", (account) => {
  // Bot successfully connected
  console.log(`Logged in as ${account.name}`);
});

bot.on("close", (reason) => {
  // Bot disconnected
  console.log(`Disconnected: ${reason}`);
});

bot.on("error", (err) => {
  // Handle errors
  console.error("Bot error:", err);
});

Next Steps

Commands Guide

Learn advanced command handling techniques

Middleware

Master the middleware system

Authentication

Explore Redis and MongoDB auth strategies

Context API

Complete context API documentation

Tips for Development

Use environment variables for sensitive data like phone numbers and database credentials.
Don’t commit the sessions folder to version control. Add it to .gitignore.
The default command prefixes are / and !. You can customize this by setting bot.prefix = "!" or any other string.

Build docs developers (and LLMs) love