
How to Replace whatsapp-web.js with whatsmeow-node
whatsapp-web.js runs a headless Chrome browser to automate WhatsApp Web. It works, but it's heavy — 200-500 MB of RAM for the browser alone, slow startup, and it breaks when WhatsApp updates their web client. whatsmeow-node uses the native multi-device protocol directly, with no browser needed.
Why Migrate?
| whatsapp-web.js | whatsmeow-node | |
|---|---|---|
| Memory | 200-500 MB (Chromium) | ~10-20 MB |
| Startup | 5-15 seconds (browser launch) | Under 1 second |
| Dependencies | Puppeteer + Chromium | Single Go binary (included) |
| Protocol | Web client automation | Native multi-device |
| Stability | Breaks on WhatsApp Web updates | Stable upstream (whatsmeow) |
| Docker image | ~1 GB+ (needs Chrome) | ~50 MB |
| TypeScript | Community types | Native TypeScript |
Step 1: Install
# Remove whatsapp-web.js and Puppeteer
npm uninstall whatsapp-web.js puppeteer
# Install whatsmeow-node
npm install @whatsmeow-node/whatsmeow-node
You can also remove any Chromium-related dependencies or Docker layers.
Step 2: Update Client Initialization
whatsapp-web.js
const { Client, LocalAuth } = require("whatsapp-web.js");
const client = new Client({
authStrategy: new LocalAuth(),
puppeteer: {
headless: true,
args: ["--no-sandbox"],
},
});
client.on("qr", (qr) => {
qrcode.generate(qr, { small: true });
});
client.on("ready", () => {
console.log("Client is ready!");
});
client.initialize();
whatsmeow-node
import { createClient } from "@whatsmeow-node/whatsmeow-node";
import qrcode from "qrcode-terminal";
const client = createClient({ store: "session.db" });
async function main() {
const { jid } = await client.init();
if (jid) {
await client.connect();
console.log("Client is ready!");
} else {
client.on("qr", ({ code }) => qrcode.generate(code, { small: true }));
await client.getQRChannel();
await client.connect();
}
}
main().catch(console.error);
No Puppeteer config, no auth strategy, no initialize() — just init() + connect().
Step 3: Update Event Listeners
Messages
whatsapp-web.js:
client.on("message", async (msg) => {
if (msg.fromMe) return;
console.log(`${msg.from}: ${msg.body}`);
if (msg.body === "!ping") {
await msg.reply("pong");
}
});
whatsmeow-node:
client.on("message", async ({ info, message }) => {
if (info.isFromMe) return;
const text =
(message.conversation as string) ??
(message.extendedTextMessage as { text?: string } | undefined)?.text;
if (!text) return;
console.log(`${info.chat}: ${text}`);
if (text === "!ping") {
await client.sendMessage(info.chat, { conversation: "pong" });
}
});
Key differences:
- No
msg.bodyshortcut — text is extracted from the protobuf message - No
msg.reply()— usesendMessage()orsendRawMessage()withcontextInfofor quotes msg.from→info.chat,msg.fromMe→info.isFromMe
Connection events
whatsapp-web.js:
client.on("ready", () => console.log("Ready"));
client.on("disconnected", (reason) => console.log("Disconnected:", reason));
whatsmeow-node:
client.on("connected", ({ jid }) => console.log(`Connected as ${jid}`));
client.on("disconnected", () => console.log("Disconnected"));
client.on("logged_out", ({ reason }) => console.error(`Logged out: ${reason}`));
Step 4: Update Message Sending
Text
whatsapp-web.js:
await client.sendMessage(chatId, "Hello!");
whatsmeow-node:
await client.sendMessage(jid, { conversation: "Hello!" });
Reply with quote
whatsapp-web.js:
await msg.reply("Got it!");
whatsmeow-node:
await client.sendRawMessage(info.chat, {
extendedTextMessage: {
text: "Got it!",
contextInfo: {
stanzaId: info.id,
participant: info.sender,
quotedMessage: { conversation: originalText },
},
},
});
Media
whatsapp-web.js:
const media = MessageMedia.fromFilePath("/path/to/photo.jpg");
await client.sendMessage(chatId, media, { caption: "Check this out" });
whatsmeow-node:
const media = await client.uploadMedia("/path/to/photo.jpg", "image");
await client.sendRawMessage(jid, {
imageMessage: {
URL: media.URL,
directPath: media.directPath,
mediaKey: media.mediaKey,
fileEncSHA256: media.fileEncSHA256,
fileSHA256: media.fileSHA256,
fileLength: String(media.fileLength),
mimetype: "image/jpeg",
caption: "Check this out",
},
});
Reactions
whatsapp-web.js:
await msg.react("👍");
whatsmeow-node:
await client.sendReaction(chat, sender, messageId, "👍");
Step 5: Update Group Operations
| whatsapp-web.js | whatsmeow-node |
|---|---|
client.createGroup(name, members) | client.createGroup(name, members) |
chat.fetchMessages() | — (use message event) |
groupChat.addParticipants([id]) | client.updateGroupParticipants(jid, [id], "add") |
groupChat.removeParticipants([id]) | client.updateGroupParticipants(jid, [id], "remove") |
groupChat.promoteParticipants([id]) | client.updateGroupParticipants(jid, [id], "promote") |
groupChat.setSubject(name) | client.setGroupName(jid, name) |
groupChat.setDescription(desc) | client.setGroupDescription(jid, desc) |
groupChat.leave() | client.leaveGroup(jid) |
groupChat.getInviteCode() | client.getGroupInviteLink(jid) |
Step 6: Clean Up
After migrating, you can remove from your project:
puppeteer/puppeteer-coredependency- Chromium download scripts or Docker layers
.wwebjs_auth/directory (LocalAuth session data)- Any
--no-sandbox,--disable-gpuChrome flag configuration - Browser-specific CI/CD setup (Xvfb, etc.)
Your Docker images will shrink dramatically without Chromium.
JID Format Differences
whatsapp-web.js uses number@c.us for contacts. whatsmeow-node uses number@s.whatsapp.net:
// whatsapp-web.js
const chatId = "5512345678@c.us";
// whatsmeow-node
const jid = "5512345678@s.whatsapp.net";
Group JIDs remain the same format: 120363XXXXX@g.us.
Migration Checklist
- Install
@whatsmeow-node/whatsmeow-node, removewhatsapp-web.jsandpuppeteer - Replace
new Client()→createClient() - Remove Puppeteer config and
LocalAuth - Update event listeners (different event names and shapes)
- Replace
msg.bodywith protobuf message extraction - Replace
msg.reply()withsendMessage()/sendRawMessage() - Replace
client.sendMessage(id, text)withsendMessage(jid, { conversation: text }) - Update JID format:
@c.us→@s.whatsapp.net - Update media sending (separate upload + send)
- Remove Chromium from Docker / CI
- Re-pair via QR code
- Test all message types end-to-end
Common Pitfalls
whatsapp-web.js sessions cannot be migrated. You must pair fresh by scanning the QR code. Your WhatsApp data is unaffected.
msg.bodywhatsapp-web.js gives you msg.body as a convenience. whatsmeow-node gives you the raw protobuf, so text could be in message.conversation or message.extendedTextMessage.text. Always check both.
whatsapp-web.js uses @c.us for contacts. whatsmeow-node uses @s.whatsapp.net. If you have stored JIDs, update them.





