1. Project Overview
WPPConnect is an open-source Node.js library that automates and extends WhatsApp Web. It simplifies messaging workflows, supports multiple sessions, handles rich media, and exposes Business API–style features for developers building chatbots, CRMs, and analytics tools.
1.1 Key Capabilities
- WhatsApp Web Automation
• Send and receive messages, react to events, manage chats
• Automate contact retrieval, group management, labels - Multi-Session Support
• Run multiple WhatsApp accounts in parallel
• Isolated session data per client - Rich Media Handling
• Send images, audio, video, documents, stickers
• Download and process incoming attachments - Business API–Style Features
• Template message support
• Message scheduling and retries
• Session lifecycle hooks and status monitoring
1.2 When to Use WPPConnect
- Build chatbots or customer-service integrations on WhatsApp
- Integrate WhatsApp messaging into web or desktop apps
- Automate marketing workflows, notifications, or alerts
- Manage multiple WhatsApp accounts from a single Node.js process
1.3 Technical Details
- Supported Node.js versions: 12.x, 14.x, 16.x (LTS)
- License: GNU Lesser General Public License v3 (LGPL-3.0)
- Funding: Community-driven; contributions and sponsorships welcome
1.4 High-Level Feature List
- Real-time message events (onMessage, onAck, onParticipantsChanged)
- Chat and contact CRUD operations
- Group creation, invitation, and role management
- Media upload/download with progress callbacks
- Session management: create, list, kill sessions
- Webhook and plugin hooks for custom logic
- Built-in rate-limit handling and reconnect strategies
Start by installing the package and initializing a session:
npm install @wppconnect-team/wppconnect
const wppconnect = require('@wppconnect-team/wppconnect');
wppconnect.create({
session: 'my-session', // arbitrary session name
headless: true, // run in headless Chrome
puppeteerOptions: { args: ['--no-sandbox'] }
})
.then(client => {
client.onMessage(async message => {
console.log('Received:', message.body);
await client.sendText(message.from, 'Hello from WPPConnect!');
});
})
.catch(err => console.error('Init error:', err));
## 2. Quick Start
Get up and running in minutes: install the package, satisfy browser prerequisites, spin up your first client, scan the QR code, and send your first message.
### 2.1 Installation
Install the latest stable release:
npm install @wppconnect-team/wppconnect
Or with Yarn:
yarn add @wppconnect-team/wppconnect
To try the cutting-edge nightly build:
npm install @wppconnect-team/wppconnect@nightly
// or
yarn add @wppconnect-team/wppconnect@nightly
### 2.2 Browser & System Prerequisites
• Node.js 14+
• Chromium or Chrome installed (v100+ recommended)
• Optional: ffmpeg on PATH for media encoding/decoding
If Puppeteer cannot find your browser, set the `PUPPETEER_EXECUTABLE_PATH` environment variable:
export PUPPETEER_EXECUTABLE_PATH="/path/to/chrome"
### 2.3 First WhatsApp Message
Create a new file `index.js`:
```javascript
// index.js
const { create } = require('@wppconnect-team/wppconnect');
async function start() {
// Initialize client with a custom session name
const client = await create({
session: 'my-first-session',
headless: true, // set to false to watch the browser
qrTimeout: 0, // disable QR timeout
});
// Send a text message to a contact
const chatId = '5511999999999@c.us'; // Brazil example: country code + number + '@c.us'
await client.sendText(chatId, 'Hello from WPPConnect!');
console.log('✅ Message sent to', chatId);
// Optionally: close client when done
await client.close();
}
start().catch(console.error);
Run:
node index.js
2.4 QR Code Flow & Troubleshooting
• On first run, the terminal prints an ASCII QR code. Scan it with your phone’s WhatsApp “Linked Devices” > “Link a Device.”
• If you don’t see a QR:
– Ensure headless: false
to display the browser window.
– Verify your Chrome/Chromium path or PUPPETEER_EXECUTABLE_PATH
.
– Check network/firewall rules blocking web.whatsapp.com
.
• Common states emitted by the client:
client.onStateChanged(state => console.log('⚙️ State:', state));
// e.g., 'PAIRING', 'CONNECTED', 'DISCONNECTED'
• If authentication fails continuously, delete the session folder under your project directory and restart.
2.5 Where to Ask for Help
• GitHub Issues: https://github.com/wppconnect-team/wppconnect/issues
• Discussions & feature requests: https://github.com/wppconnect-team/wppconnect/discussions
• Community Chat (Discord invite on repo README)
• Stack Overflow: Tag your question wppconnect
Welcome aboard! You’ve just sent your first WhatsApp message via WPPConnect.
3. Core Concepts
Fundamental ideas and configuration points for any serious use of WPPConnect. Each subsection explains one core feature of the library.
Scraping WhatsApp QR Code Image
Purpose: Retrieve the current WhatsApp Web QR code as a base64 image and its raw URL for custom handling (UI display, disk saving, ASCII generation).
Description
The scrapeImg
helper automates DOM queries to locate the <canvas>
element containing the QR code and its parent [data-ref]
URL attribute. It will:
- Click the “reload” button if present to force a fresh QR.
- Wait until the new
data-ref
attribute populates. - Extract:
•
base64Image
: the canvas image as a Data URL
•urlCode
: the raw QR URL string
Function signature
import { Page } from 'puppeteer';
import { ScrapQrcode } from '../model/qrcode';
async function scrapeImg(page: Page): Promise<ScrapQrcode | undefined>
ScrapQrcode shape
base64Image
: stringurlCode
: string
Code example: standalone usage
import puppeteer from 'puppeteer';
import { scrapeImg } from 'wppconnect/api/helpers/scrape-img-qr';
async function fetchQr() {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://web.whatsapp.com');
// Wait until the QR canvas appears
await page.waitForSelector('canvas');
const qrData = await scrapeImg(page);
if (qrData) {
console.log('QR URL:', qrData.urlCode);
// Strip prefix if needed
const base64 = qrData.base64Image.split(',')[1];
require('fs').writeFileSync('qr.png', base64, 'base64');
} else {
console.error('Failed to scrape QR code');
}
await browser.close();
}
Integration in HostLayer
The HostLayer.getQrCode()
method simply calls scrapeImg
. Override the catchQR
callback to receive QR data on each scan request:
import { create } from 'wppconnect';
import { ScrapQrcode } from 'wppconnect/api/model/qrcode';
create({
session: 'mySession',
catchQR: (base64Image, asciiQr, attempt, urlCode) => {
console.log(`Attempt ${attempt}: ${urlCode}`);
// Display asciiQr in terminal or UI
}
}).then(client => {
// client is ready once logged in
});
Practical guidance
- Always call
page.waitForSelector('canvas')
beforescrapeImg
. - To generate ASCII QR codes, use
asciiQr(result.urlCode)
. - Handle
undefined
returns when the DOM structure changes. scrapeImg
clicks the reload button under the same[data-ref]
container.- Use
HostLayer.waitForQrCodeScan()
afterstart()
to block until the scan completes.
FileTokenStore
Purpose: Persist session tokens on the filesystem with customizable encoding, decoding, directory, and extension.
Instantiation and Options
By default, FileTokenStore
serializes tokens as JSON under ./tokens/*.data.json
:
import { FileTokenStore } from '@wppconnect-team/wppconnect/token-store';
const store = new FileTokenStore();
// default options:
// {
// decodeFunction: JSON.parse,
// encodeFunction: JSON.stringify,
// encoding: 'utf8',
// fileExtension: '.data.json',
// path: './tokens',
// }
Customize directory, extension, or serialization:
import YAML from 'yaml';
const customStore = new FileTokenStore({
path: './my_tokens',
fileExtension: '.session.json',
encoding: 'utf16',
decodeFunction: txt => YAML.parse(txt),
encodeFunction: obj => YAML.stringify(obj),
});
API Methods
resolverPath(sessionName: string): string
Returns an absolute path for the token file.getToken(sessionName: string): Promise<SessionToken | undefined>
Reads and decodes the token or returnsundefined
if missing or invalid.setToken(sessionName: string, tokenData: SessionToken | null): Promise<boolean>
Validates viaisValidSessionToken
, creates directories, writes encoded data.removeToken(sessionName: string): Promise<boolean>
Deletes the token file; returnstrue
if removed.listTokens(): Promise<string[]>
Lists files by extension and returns session names without extensions.
Practical Usage
import { FileTokenStore } from '@wppconnect-team/wppconnect/token-store';
import { create } from '@wppconnect-team/wppconnect';
const tokenStore = new FileTokenStore({ path: './sessions' });
// Initialize a session with file storage
await create({ session: 'userA', tokenStore });
// Later, retrieve and inspect the token
const token = await tokenStore.getToken('userA');
console.log('Loaded token for userA:', token);
// List active sessions
const sessions = await tokenStore.listTokens();
console.log('Active sessions:', sessions);
// Remove a session on logout
const removed = await tokenStore.removeToken('userA');
console.log('Session userA removed?', removed);
Tips and Gotchas
- Corrupted token files are ignored and treated as missing.
- Always
await setToken
and check its boolean return. - Custom
encodeFunction
must return a string. - Nested directory creation uses
fs.promises.mkdir(..., { recursive: true })
.
Controls Layer
Purpose: Manage contacts, chats, messages, group settings, and Web session limits.
Blocklist
blockContact(contactId: string): Promise<boolean>
unblockContact(contactId: string): Promise<boolean>
Example
await client.blockContact('1234567890@c.us');
await client.unblockContact('1234567890@c.us');
Chat Management
markUnseenMessage(chatId: string): Promise<boolean>
deleteChat(chatId: string): Promise<boolean>
archiveChat(chatId: string, option = true): Promise<boolean>
pinChat(chatId: string, option: boolean, nonExistent?: boolean): Promise<object>
clearChat(chatId: string, keepStarred = true): Promise<boolean>
Example
await client.markUnseenMessage('123@c.us');
const ok = await client.deleteChat('123@c.us');
if (ok) console.log('Chat deleted');
await client.archiveChat('123@c.us', true);
await client.pinChat('123@c.us', true, true);
await client.clearChat('123@c.us');
Message Operations
deleteMessage(chatId: string, messageId: string[]|string, onlyLocal?: boolean, deleteMediaInDevice?: boolean): Promise<boolean>
editMessage(msgId: string, newText: string, options?): Promise<Message>
starMessage(messageId: string[]|string, star?: boolean): Promise<number>
Example
await client.deleteMessage('123@c.us', 'true_123@c.us_ABC', true);
const edited = await client.editMessage('true_123@c.us_ABC', 'Updated text');
console.log('New body:', edited.body);
await client.starMessage('true_123@c.us_ABC', true);
Group Settings
setMessagesAdminsOnly(chatId: string, option: boolean): Promise<boolean>
setTemporaryMessages(chatOrGroupId: string, value: boolean): Promise<boolean>
Example
await client.setMessagesAdminsOnly('999999@g.us', true);
await client.setTemporaryMessages('123@c.us', true);
Connection Limits
setLimit(key: 'maxMediaSize' | 'maxFileSize' | 'maxShare' | 'statusVideoMaxDuration' | 'unlimitedPin', value: any): Promise<any>
Example
await client.setLimit('maxMediaSize', 50 * 1024 * 1024); // 50MB
await client.setLimit('unlimitedPin', true);
Practical Tips
- Check boolean or object returns for success.
- Use
nonExistent=true
inpinChat
to auto-create missing chats. - Temporary messages apply to individual and group chats.
- Adjust limits sparingly to avoid Web instability.
Event Registration with ListenerLayer
Purpose: Subscribe to real-time WhatsApp events using the ListenerLayer API and dispose listeners when done.
1. Core Mechanism: registerEvent
ListenerLayer wraps a Node.js EventEmitter
to forward browser‐side WAPI events.
protected registerEvent(event: string|symbol, listener: (...args:any[])=>void) {
this.log('debug', `Registering ${event.toString()} event`);
this.listenerEmitter.on(event, listener);
return { dispose: () => this.listenerEmitter.off(event, listener) };
}
2. Commonly Used Event Methods
onMessage(callback: Message)
– incoming messages only.onAnyMessage(callback: Message)
– all new messages.onNotificationMessage(callback: Message)
– system notifications.onAck(callback: Ack)
– delivery/read acknowledgements.onIncomingCall(callback: IncomingCall)
– incoming calls.onPresenceChanged(id?, callback: PresenceEvent)
– presence updates (requiressubscribePresence
).onLiveLocation(id?, callback: LiveLocation)
– live‐location updates.
Each returns { dispose(): void }
to stop listening.
3. Usage Examples
import { Client } from 'wppconnect';
const client = await Client.create();
const listener = client.getListener();
// Incoming messages
const msgHandle = listener.onMessage(msg => {
console.log('New incoming message:', msg);
});
// All messages
const anyHandle = listener.onAnyMessage(msg => {
console.log('Message event:', msg.from, msg.id);
});
// Presence for specific contacts
await listener.subscribePresence(['1234@c.us','5678@c.us']);
const presHandle = listener.onPresenceChanged(['1234@c.us'], presence => {
console.log(presence.id, 'is now', presence.type);
});
// Live location in a group
const liveHandle = listener.onLiveLocation('99977-111@g.us', loc => {
console.log('Live location update:', loc);
});
// Cleanup
msgHandle.dispose();
anyHandle.dispose();
presHandle.dispose();
liveHandle.dispose();
4. Practical Tips
- Always call
dispose()
on handles to avoid memory leaks. - Call
subscribePresence
beforeonPresenceChanged
. - Use
onAnyMessage
for complete message logging. - Combine
onInterfaceChange
andonStateChange
to monitor UI and connection.
Custom Log Labels (Session and Type)
Purpose: Prefix log messages with [session:type]
metadata using the formatLabelSession
transformer.
How It Works
In src/utils/logger.ts
:
export const formatLabelSession: FormatWrap = format((info: SessionInfo) => {
const parts: string[] = [];
if (info.session) { parts.push(info.session); delete info.session; }
if (info.type) { parts.push(info.type); delete info.type; }
if (parts.length) {
info.message = `[${parts.join(':')}] ${info.message}`;
}
return info;
});
The default logger combines this with colorizing and padding:
export const defaultLogger = createLogger({
level: 'silly',
levels: config.npm.levels,
format: format.combine(
formatLabelSession(),
format.colorize(),
format.padLevels(),
format.simple()
),
transports: [new transports.Console()],
});
Usage Examples
const wppconnect = require('@wppconnect-team/wppconnect');
// Prints: info: [mySession:connect] Client connected successfully
wppconnect.defaultLogger.info('Client connected successfully', {
session: 'mySession',
type: 'connect'
});
// Prefix [mySession]
wppconnect.defaultLogger.warn('Reconnecting...', {
session: 'mySession'
});
// Prefix [:cleanup]
wppconnect.defaultLogger.debug('Cleaning up resources', {
type: 'cleanup'
});
Custom Winston logger:
const winston = require('winston');
const { formatLabelSession } = require('@wppconnect-team/wppconnect/src/utils/logger');
const customLogger = winston.createLogger({
level: 'debug',
format: winston.format.combine(
formatLabelSession(),
winston.format.json()
),
transports: [new winston.transports.Console()]
});
customLogger.info('Initializing', { session: 'svc123', type: 'init' });
Practical Tips
- Always include
session
and/ortype
in log metadata. - If you remove
formatLabelSession()
from the chain, metadata fields log as JSON. - Extend or modify
formatLabelSession
for custom label syntax by inserting your formatter into theformat.combine
chain.
4. Usage Examples
Basic Bot Setup and Message Handling
Demonstrates how to initialize a WPPConnect client, listen for incoming one-to-one messages, and send a simple reply.
const wppconnect = require('../../dist');
wppconnect
.create()
.then(client => start(client))
.catch(err => console.error(err));
function start(client) {
client.onMessage(message => {
if (message.body === 'Hi' && !message.isGroupMsg) {
client
.sendText(message.from, 'Welcome to WPPConnect')
.then(response => {
console.log('Message sent:', response.id);
})
.catch(error => {
console.error('Send error:', error);
});
}
});
}
Practical Usage:
- Use a WhatsApp Business account; regular accounts aren’t supported.
- Scan the QR code on first run; subsequent runs reuse the session token.
- Run:
node examples/basic/index.js
- Customize the trigger (
message.body
) and reply text. - Persist session data and implement reconnection logic in production.
Bot Functions Example
Implements common commands (ping, send, pin, typing, chat state, buttons) in a simple bot.
Setup & Run
npm install
cd examples/bot-functions
npm start
Core Initialization
const wppconnect = require('../../dist');
wppconnect
.create({
session: 'test',
onLoadingScreen: (percent, message) => {
console.log('LOADING_SCREEN', percent, message);
},
})
.then(client => start(client))
.catch(console.error);
function start(client) {
console.log('Starting bot...');
client.onMessage(handleMessage);
}
Command Handler
async function handleMessage(msg) {
const from = msg.from;
const text = msg.body || '';
try {
if (text === '!ping') {
return client.sendText(from, 'pong');
}
if (text === '!ping reply') {
return client.reply(from, 'pong', msg.id.toString());
}
if (text === '!chats') {
const chats = await client.getAllChats();
return client.sendText(from, `Open chats: ${chats.length}`);
}
if (text === '!info') {
const info = await client.getHostDevice();
const message =
`_*Connection info*_ \n` +
`• Name: ${info.pushname}\n` +
`• Number: ${info.wid.user}\n` +
`• Battery: ${info.battery}%\n` +
`• Device: ${info.phone.device_manufacturer}\n` +
`• WA Version: ${info.phone.wa_version}`;
return client.sendText(from, message);
}
if (text.startsWith('!sendto ')) {
let [_, number, ...msgParts] = text.split(' ');
const message = msgParts.join(' ');
if (!number.includes('@c.us')) number += '@c.us';
return client.sendText(number, message);
}
if (text.startsWith('!pin ')) {
const flag = text.split(' ')[1] === 'true';
return client.pinChat(from, flag);
}
if (text.startsWith('!typing ')) {
const flag = text.split(' ')[1] === 'true';
return flag ? client.startTyping(from) : client.stopTyping(from);
}
if (text.startsWith('!ChatState ')) {
const state = text.split(' ')[1]; // 0: Typing, 1: Recording, 2: Paused
return client.setChatState(from, state);
}
if (text === '!btn') {
return client.sendMessageOptions(from, 'Choose:', {
title: 'Product Options',
footer: 'Select below',
isDynamicReplyButtonsMsg: true,
dynamicReplyButtons: [
{ buttonId: 'yes', buttonText: { displayText: 'YES' }, type: 1 },
{ buttonId: 'no', buttonText: { displayText: 'NO' }, type: 1 },
],
});
}
} catch (error) {
console.error('Command error', error);
}
}
Practical Tips:
- Wrap each command in its own
if
block for clarity. - Use
sendText
for basic replies andreply
to reference the original message. - Append
@c.us
to numbers when messaging arbitrary contacts. - Explore methods like
sendImage
andsendFile
to extend the bot. - Catch errors to prevent crashes on unexpected input.
REST API Example for WPPConnect Client
Exposes WPPConnect operations (connection status, text message, PIX) via an Express REST interface.
1. Setup
npm install express @wppconnect-team/wppconnect
node index.js
Scan the QR code printed in the console to establish the session.
2. Initialization (index.js
)
const express = require('express');
const wppconnect = require('@wppconnect-team/wppconnect');
const app = express();
let clientInstance;
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
wppconnect
.create({
session: 'teste',
headless: true,
logQR: true,
autoClose: 60000,
folderNameToken: './tokens'
})
.then(client => { clientInstance = client; })
.catch(console.error);
app.listen(3000, () => console.log('Server on port 3000'));
3. Endpoints
GET /getconnectionstatus
Returns
{ "status": true, "message": "CONNECTED" }
POST /sendmessage
Request:
{ "telnumber": "554190000000", "message": "Hello from WPPConnect!" }
Response:
{ "status": true, "message": "<message-id>" }
POST /sendpixmessage
Request:
{
"telnumber": "554190000000",
"params": { /* PIX payload */ },
"options": { /* sendPix options */ }
}
Response format matches /sendmessage
.
4. Usage Examples
Check status:
curl http://127.0.0.1:3000/getconnectionstatus
Send text:
curl -X POST http://127.0.0.1:3000/sendmessage \
-H "Content-Type: application/json" \
-d '{"telnumber":"554190000000","message":"Hello from REST!"}'
Send PIX:
curl -X POST http://127.0.0.1:3000/sendpixmessage \
-H "Content-Type: application/json" \
-d '{
"telnumber":"554190000000",
"params": { "pixKey":"...","value":"10.00" },
"options": { "caption":"Payment QR" }
}'
Practical Tips:
- Ensure
clientInstance
is initialized before handling requests. - Append
@c.us
totelnumber
inside the server. - Use
getConnectionState()
to confirm aCONNECTED
session. - Handle promise rejections to avoid hanging HTTP requests.
Using the Newsletter Example
Initializes a client, listens for a trigger, and creates a newsletter via createNewsletter
.
1. Initialize the Client
const wppconnect = require('../../dist');
wppconnect
.create({ session: 'test' })
.then(client => start(client))
.catch(error => console.error('Initialization error', error));
2. Listen for the Trigger Message
function start(client) {
client.onMessage(async message => {
if (message.body === 'create newsletter' && !message.isGroupMsg) {
await handleCreateNewsletter(client, message);
}
});
}
3. Create the Newsletter
async function handleCreateNewsletter(client, message) {
const newsletter = await client.createNewsletter(
'WPP Test Newsletter2',
{ description: 'test' }
);
console.log('Newsletter created:', newsletter);
await client.sendText(
message.from,
'```' + JSON.stringify(newsletter, null, 2) + '```'
);
await client.sendText(
message.from,
'✅ Newsletter created. Check channels on your device.'
);
}
4. Practical Usage
node examples/newsletter/index.js
- Send
create newsletter
in a private chat to your bot. - Receive code-formatted JSON and a confirmation.
- Extend payloads with images or links.
- Wrap
createNewsletter
intry/catch
to handle errors.
Order Message Handling Example
Shows how to send an order, retrieve details, and listen for status updates.
Sending an Order Message
if (message.body === 'new order' && !message.isGroupMsg) {
const items = [
{
type: 'custom',
name: 'Item with cost test',
price: 120000,
qnt: 2
}
];
const order = await client.sendOrderMessage(message.from, items);
froms.push(message.from);
await client.sendText(message.from, 'Save your order ID for status checks:');
await client.sendText(message.from, order.id);
}
Retrieving Order Details
if (message.body?.startsWith('order id=') && !message.isGroupMsg) {
const orderId = message.body.split('=')[1].trim();
const orderDetails = await client.getOrder(orderId);
await client.sendText(
message.from,
'```json\n' + JSON.stringify(orderDetails, null, 2) + '\n```'
);
}
Listening for Order Status Updates
client.onOrderStatusUpdate(update => {
froms.forEach(async chatId => {
await client.sendText(
chatId,
'```json\n' + JSON.stringify(update, null, 2) + '\n```'
);
});
});
Practical Usage Tips:
- Trigger order flows only in private chats.
- Persist
order.id
for status checks. - Specify prices in the smallest currency unit (e.g., cents).
- Maintain a list of chat IDs (
froms
) for broadcasts. - Wrap JSON payloads in code fences for readability.
5. API Reference
This section explains how to access and navigate the automatically generated TypeDoc documentation for WPPConnect. It links to the hosted docs, shows how library layers map to TypeScript namespaces, and offers tips for reading the API types.
5.1 Accessing the Hosted Docs
The public API docs are published to GitHub Pages. View them at:
https://wppconnect-team.github.io/wppconnect/api-docs/
Use the search box or the sidebar to jump to modules, namespaces, interfaces or classes.
5.2 Generating the Docs Locally
Run the following to regenerate TypeDoc output under api-docs/
:
npm run docs
# or directly
npx typedoc --options typedoc.json
Key typedoc.json
settings:
entryPoints
: src/index.tsout
: api-docsexcludePrivate
: trueincludeVersion
: true
5.3 Layers → Namespaces Mapping
WPPConnect organizes its public API into logical layers, each exposed as a TypeScript namespace:
• Core
• Namespace: WPPConnect
• Exports: initialize
, create
, main client class
• Configuration
• Namespace: WPPConnect.Config
• Types: IConfig
, ConfigOptions
, session settings
• Models
• Namespace: WPPConnect.Models
• Types: Contact
, Chat
, Message
, etc.
• Logging
• Namespace: WPPConnect.Logging
• Functions: setLogLevel
, Logger
, LogLevel
enum
• Tokens
• Namespace: WPPConnect.Tokens
• Types: ITokenStore
, TokenData
, token management APIs
5.4 Reading the TypeScript Types
- Click a namespace in the sidebar (e.g. WPPConnect.Config).
- Select an interface or function to view:
- Parameters
- Return type
- Example usage (when provided)
Example: initialize
signature
// src/index.ts excerpt
export function initialize(
options: Config.IConfig
): Promise<WPPConnect>;
In the docs you’ll see:
- Parameters
•options: Config.IConfig
– all configuration flags - Returns
•Promise<WPPConnect>
– a ready-to-use client instance
5.5 Quick Navigation Tips
- Use the global search (top right) to find types or methods.
- Switch between Modules and Namespaces view for hierarchical or flat listing.
- For method overloads, expand the declaration to see all signatures.
- Click the “Source” link to jump to the exact TypeScript definition in GitHub.
5.6 Common Reference Patterns
Initialize a client and inspect its methods:
import { initialize, Config } from '@wppconnect-team/wppconnect';
const client = await initialize({
session: 'user123',
headless: true,
logLevel: 'info'
} as Config.IConfig);
// Hover over `client` in your editor to see all methods listed in WPPConnect namespace
await client.sendText('123456789@c.us', 'Hello from WPPConnect!');
Consult the API docs for further details on each namespace, type, and method.
6. Advanced Usage
File to Base64 Conversion (src/api/helpers/file-to-base64.ts)
Purpose
Convert a local file on disk into a Base64-encoded data URI, automatically detecting its MIME type (or accepting an override). Used throughout the sender layer to inline images, audio, documents, etc.
Functions
fileToBase64(path: string, mime?: string | false): Promise<string | false>
Mine(path: string): Promise<string | false>
– Returns only the MIME type viamime-types.lookup
How it works
- Check if the file exists on disk (
fs.existsSync
). - Read the file into a Base64 string (
fs.readFileSync(path, { encoding: 'base64' })
). - Determine the MIME type:
- If you pass
mime
, use it directly. - Otherwise try
mime-types.lookup(path)
. - If that fails, probe the file contents with
file-type.fromFile(path)
. - Fallback to
application/octet-stream
.
- If you pass
- Return a data URI:
data:<mime>;base64,<base64Payload>
- If the file doesn’t exist, returns
false
.
Usage Examples
Importing the helpers
import { fileToBase64, Mine as getMime } from 'src/api/helpers/file-to-base64';
Basic conversion
const dataUri = await fileToBase64('./assets/logo.png');
if (!dataUri) {
throw new Error('File not found');
}
console.log(dataUri.slice(0, 30));
// → data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...
Forcing a specific MIME type
// Override auto-detection if you know the file really is image/webp
const webpUri = await fileToBase64('path/to/file', 'image/webp');
Getting only the MIME type
const mimeType = await getMime('./docs/tutorial.pdf');
if (mimeType) {
console.log(mimeType); // e.g. "application/pdf"
}
Practical Tips
- Always handle the
false
return to catch missing files. - Use the resulting data URI directly in any WPPConnect sender method that accepts Base64 (e.g.
sendImageFromBase64
,sendFile
,sendPttFromBase64
). - To download a remote URL, call
downloadFileToBase64(url)
first, then fall back tofileToBase64
for local paths. - Passing
mime = false
behaves like omitting it—auto-detect applies in both cases.
Product Catalog Management
Purpose
Manage products and collections via the CatalogLayer: create, retrieve, update, delete.
Creating a Product
// Add a new product
const product = await client.createProduct(
'Wireless Mouse', // name
base64ImageString, // image as Base64
'Ergonomic wireless mouse', // description
29.99, // price
false, // isHidden
'https://yourstore.com/mouse', // url
'WM-001', // retailerId
'USD' // currency
);
console.log('New product ID:', product.id);
Retrieving Products
// Get first 20 products of a business
const products = await client.getProducts('123456789@c.us', 20);
products.forEach(p => {
console.log(p.name, p.price);
});
Fetching a Single Product
const product = await client.getProductById('123456789@c.us', 'WM-001');
console.log('Description:', product.description);
Editing a Product
await client.editProduct('WM-001', {
price: '24.99',
description: 'Now 20% OFF',
isHidden: false
});
Deleting Products
// Single deletion
await client.delProducts(['WM-001']);
// Batch deletion
await client.delProducts(['WM-002', 'WM-003']);
Managing Product Images
// Change main image
await client.changeProductImage('WM-001', newBase64Image);
// Add additional image
await client.addProductImage('WM-001', extraBase64Image);
// Remove additional image by index
await client.removeProductImage('WM-001', '1');
Collections
// Create a new collection
const collection = await client.createCollection(
'Summer Essentials',
['WM-001','WM-004']
);
// Get collections (limit 5 collections, max 10 products each)
const cols = await client.getCollections('123456789@c.us','5','10');
// Edit existing collection
await client.editCollection(collection.id, {
collectionName: 'Summer Sale',
productsToAdd: ['WM-005'],
productsToRemove: ['WM-004']
});
// Delete a collection
await client.deleteCollection(collection.id);
Visibility & Cart Settings
// Toggle product visibility
await client.setProductVisibility('WM-001', true);
// Enable or disable the in-chat “Add to Cart” button globally:
await client.updateCartEnabled(false);
Managing WhatsApp Communities
Purpose
Use CommunityLayer to create and manage WhatsApp Communities—collections of groups under a single umbrella.
Initialization
import { CommunityLayer } from 'wppconnect-api'; // adjust import to your setup
import { chromium } from 'playwright'; // or puppeteer
const browser = await chromium.launch();
const page = await browser.newPage();
const communityLayer = new CommunityLayer(page, 'session-id');
1. Create a Community
const groupIds = ['12345-67890@g.us', '09876-54321@g.us'];
const community = await communityLayer.createCommunity(
'Neighborhood Watch',
'Local community updates and alerts',
groupIds
);
console.log('Community created:', community.id);
Parameters:
name
: string – community namedescription
: string – community descriptiongroupIds
: string[] | Wid[] – IDs of groups to include
2. Deactivate a Community
await communityLayer.deactivateCommunity('11111-22222@g.us');
console.log('Community deactivated');
3. Add or Remove Subgroups
// Add existing groups
await communityLayer.addSubgroupsCommunity(
'11111-22222@g.us',
['33333-44444@g.us']
);
// Remove groups
await communityLayer.removeSubgroupsCommunity(
'11111-22222@g.us',
['33333-44444@g.us']
);
4. Manage Community Participants
// Promote a single participant
await communityLayer.promoteCommunityParticipant(
'11111-22222@g.us',
'99999@c.us'
);
// Demote multiple participants
await communityLayer.demoteCommunityParticipant(
'11111-22222@g.us',
['99999@c.us', '88888@c.us']
);
Parameter:
participantId
: string | string[] | Wid[] – user IDs
5. List Community Participants
const participants = await communityLayer.getCommunityParticipants('11111-22222@g.us');
participants.forEach(p => {
console.log(p.id, p.isAdmin ? 'Admin' : 'Member');
});
Practical Tips
- Always pass valid group IDs (
<digits>-<digits>@g.us
). - Participant IDs use the
<digits>@c.us
format. - Use
getCommunityParticipants
to verify membership changes. - Inspect the returned “any” shape in console to adapt your typings.
setProfilePic: Update User Profile Picture
Sets the current user’s profile image from a local file, URL or Base64 string. Validates input, enforces allowed image types, and generates two resized thumbnails (96×96 and 640×640) before uploading via WAPI.
Parameters
- pathOrBase64: string
- Local file path (
./avatar.png
), remote URL (https://…/pic.jpg
) or data URI (data:image/jpeg;base64,…
).
- Local file path (
- to?: string (optional)
- Chat ID for a business account or other target. Defaults to your own profile.
Errors
- code: "empty_file" — no file or Base64 data found.
- code: "invalid_image" — MIME type is not an image (allowed: png, jpg, jpeg, webp, gif).
Usage example
// 1. From a local file
await client.profile.setProfilePic('./assets/me.png')
.then(() => console.log('Profile picture updated'))
.catch(err => console.error(err.code, err.message));
// 2. From a remote URL
await client.profile.setProfilePic('https://example.com/avatar.jpg');
// 3. From an in-memory Base64 string
const base64 = await fs.promises.readFile('./me.webp', 'base64');
await client.profile.setProfilePic(`data:image/webp;base64,${base64}`);
// 4. For another target (e.g., business profile)
await client.profile.setProfilePic('./logo.png', '12345@g.us');
How it works
- Detects if input starts with
data:
. If not, attemptsdownloadFileToBase64
from URL orfileToBase64
from disk. - Throws
empty_file
if no Base64 obtained. - Checks MIME via
base64MimeType
, throwsinvalid_image
if not an image. - Strips the
data:image/...;base64,
header, decodes to aBuffer
. - Uses
resizeImg
to create two sizes:- a: 640×640
- b: 96×96
- Calls
WAPI.setProfilePic({ a, b }, to)
inside the page context.
Tips
- Ensure your image is at least 640×640 for best quality.
- Handle errors by inspecting
err.code
. - Thumbnails are mandatory for WhatsApp Web’s avatar previews.
Decrypting WhatsApp Media Streams
Purpose
Show how to decrypt media downloaded from WhatsApp Web using the magix
(synchronous) and newMagix
(streaming) helpers in src/api/helpers/decrypt.ts
.
Essential Concepts
- WhatsApp messages include a Base64-encoded
mediaKey
and encrypted payload. magix
returns a fully decryptedBuffer
when the entire file is in memory.newMagix
returns a Node.jsTransform
stream for on-the-fly decryption of large files.
- magix (synchronous decryption)
Accepts:
fileData
:Buffer
orArrayBuffer
of encrypted bytesmediaKeyBase64
: string from message metadatamediaType
: one ofImage
,Video
,Audio
,Document
,Sticker
,PTT
expectedSize?
: number for padding correction
Returns a decrypted Buffer
.
Example:
import axios from 'axios';
import { makeOptions, magix } from './helpers/decrypt';
import * as fs from 'fs';
async function fetchAndDecrypt(
url: string,
mediaKey: string,
mediaType: string,
expectedSize: number
) {
// Step 1: Download encrypted data
const opts = makeOptions('MyApp/1.0');
const response = await axios.get<ArrayBuffer>(url, opts);
const encrypted = Buffer.from(response.data);
// Step 2: Decrypt
const decrypted: Buffer = magix(encrypted, mediaKey, mediaType, expectedSize);
// Step 3: Save or process
await fs.promises.writeFile(`./output.${mediaType.toLowerCase()}`, decrypted);
}
- newMagix (streaming decryption)
Returns aTransform
stream you can pipe directly from an HTTP response or file read stream.
Example:
import axios from 'axios';
import * as fs from 'fs';
import { makeOptions, newMagix } from './helpers/decrypt';
async function streamDecryptToFile(
url: string,
mediaKey: string,
mediaType: string,
expectedSize: number,
destPath: string
) {
// Download encrypted stream
const opts = makeOptions('MyApp/1.0', 'stream');
const response = await axios.get<NodeJS.ReadableStream>(url, opts);
// Create decrypt stream
const decryptStream = newMagix(mediaKey, mediaType, expectedSize);
// Pipe network → decrypt → file
response.data.pipe(decryptStream).pipe(fs.createWriteStream(destPath));
// Await completion
await new Promise((resolve, reject) => {
decryptStream.on('end', resolve);
decryptStream.on('error', reject);
});
}
Practical Guidance
- Supply the correct
expectedSize
when known to ensure proper PKCS#7 padding trimming. - Use
magix
for small media (<10 MB) andnewMagix
for larger files or direct streaming. - Call
makeOptions
to apply required headers and UA override when fetching from WhatsApp servers. - Ensure
futoin-hkdf
,atob
and Node’scrypto
are installed and up to date.
7. Internals & Architecture
Deep dive into WPPConnect’s core engine components: how we launch and manage browsers, bootstrap WhatsApp Web, inject APIs, handle sessions, and route events between Node.js and the browser page.
7.1 BrowserController
Manages Puppeteer browser lifecycle, page reuse, and temporary profile cleanup.
initBrowser
Initializes or connects to Chrome/Chromium with WPPConnect defaults.
import { Browser, launch, connect } from 'puppeteer-core';
import { StealthPlugin } from 'puppeteer-extra-plugin-stealth';
import { chromeLauncher } from 'chrome-launcher';
async function initBrowser(
session: string,
options: CreateConfig,
logger: Logger
): Promise<Browser> {
// 1. Locate Chrome if useChrome=true
if (options.useChrome) {
const detected = await chromeLauncher.Launcher.getInstallations();
options.puppeteerOptions.executablePath = detected[0];
}
// 2. Apply stealth
const puppeteerExtra = require('puppeteer-extra');
puppeteerExtra.use(StealthPlugin());
// 3. Connect or launch
let browser: Browser;
if (options.browserWS) {
browser = await connect({ browserWSEndpoint: options.browserWS, ...options.puppeteerOptions });
} else {
const args = [...(options.browserArgs || []), ...defaultChromiumArgs()];
if (options.proxy) args.push(`--proxy-server=${options.proxy.url}`);
browser = await puppeteerExtra.launch({
headless: options.headless,
devtools: options.devtools,
args,
...options.puppeteerOptions
});
}
// 4. Schedule cleanup of temp user-data-dir
scheduleProfileCleanup(browser, logger);
return browser;
}
getOrCreatePage
Reuses an existing tab or opens a new one. Ensures only one WhatsApp Web page per session.
const pages = new Map<string, Page>();
export async function getOrCreatePage(browser: Browser, session: string): Promise<Page> {
if (pages.has(session)) {
const existing = pages.get(session)!;
if (!existing.isClosed()) return existing;
}
const page = await browser.newPage();
await page.setUserAgent(customUserAgent());
pages.set(session, page);
return page;
}
Practical tips
• Customize default Chromium flags in src/config/puppeteer.config.ts
→ chromiumArgs
• Append flags at runtime via options.browserArgs
• Always call getOrCreatePage
after initBrowser
to avoid duplicate tabs
7.2 SessionManager
Persists and restores authentication state (cookies, localStorage) between runs.
saveSession
Serializes cookies and localStorage to disk:
import fs from 'fs/promises';
export async function saveSession(page: Page, sessionId: string) {
const cookies = await page.cookies();
const localStorage = await page.evaluate(() => JSON.stringify(window.localStorage));
await fs.writeFile(`${sessionId}.session.json`, JSON.stringify({ cookies, localStorage }));
}
restoreSession
Loads from disk and applies to a fresh page:
export async function restoreSession(page: Page, sessionId: string) {
try {
const data = JSON.parse(await fs.readFile(`${sessionId}.session.json`, 'utf-8'));
await page.setCookie(...data.cookies);
await page.evaluate(ls => {
Object.entries(JSON.parse(ls)).forEach(([k, v]) => localStorage.setItem(k, v as string));
}, data.localStorage);
} catch {
// No existing session; proceed with QR auth
}
}
7.3 WAPI Injection & RPC
Injects WPPConnect’s in-page API (WAPI) for messaging, contacts, and media.
Injection Flow
- After page load, wait for WhatsApp Web’s
webpackJsonp
bundle. - Inject
wapi.js
viapage.evaluateOnNewDocument
. - Expose a Node callback for events:
await page.exposeFunction('onWAPIEvent', (event) => {
// route to Node EventEmitter
eventEmitter.emit(event.type, event.payload);
});
await page.evaluateOnNewDocument(() => {
// inside browser context
window.WAPI.on = (type, handler) =>
window.addEventListener(type, ev => handler(ev.detail));
});
RPC Calls
All WAPI functions use window.postMessage
to communicate:
// Node side: send command
await page.evaluate(({ fn, args, id }) => {
window.postMessage({ type: 'WAPI_CALL', fn, args, id }, '*');
}, { fn: 'sendMessage', args: ['123@c.us', 'Hello'], id: 'req1' });
// Browser side (in wapi.js)
window.addEventListener('message', async ({ data }) => {
if (data.type === 'WAPI_CALL') {
const result = await WPP[data.fn](https://github.com/wppconnect-team/wppconnect/blob/master/...data.args);
window.postMessage({ type: 'WAPI_RESPONSE', id: data.id, result }, '*');
}
});
7.4 EventDispatcher & Message Queue
Coordinates inbound/outbound events between browser and Node.js.
- Node enqueues WAPI calls with unique IDs.
- Browser resolves calls and emits
WAPI_RESPONSE
. - Session-level queue ensures FIFO ordering and backpressure.
Example: sending a text message with guaranteed ordering
class MessageQueue {
private queue = Promise.resolve();
enqueue<T>(fn: () => Promise<T>): Promise<T> {
this.queue = this.queue.then(() => fn());
return this.queue;
}
}
// Usage
await messageQueue.enqueue(() => client.sendText('123@c.us', 'Hi'));
7.5 Plugin System & Extensions
Allows users to hook into lifecycle events or override WAPI behavior.
- Define a plugin as an object
{ name: string, onBrowser?(browser), onPage?(page), onEvent?(event) }
. - Register via
Client.use(plugin)
beforeclient.initialize()
.
client.use({
name: 'log-incoming',
onEvent(event) {
if (event.type === 'incoming_message') {
console.log('New message:', event.payload);
}
}
});
7.6 Logging & Diagnostics
Unified logging across Node and browser for easy troubleshooting.
- Node uses Winston (configured in
src/config/logger.ts
) atinfo
level by default. - Browser console logs pipe back via
page.on('console', ...)
. - Network requests (media uploads/downloads) log URLs and timings.
Enable verbose logging:
import { createLogger, transports, format } from 'winston';
const logger = createLogger({
level: 'debug',
format: format.combine(format.timestamp(), format.simple()),
transports: [new transports.Console()]
});
const client = createClient({ session: 'X', logger });
Practical tip
• Monitor browser console messages to diagnose injection failures or API mismatches.
• Use client.on('qr', ...)
and client.on('ready', ...)
hooks to track session bootstrap.
8. Contribution & Development
Overview
This section guides you through cloning the repo, installing dependencies, running tests, linting code, and submitting pull requests to WPPConnect.
Setup
- Clone the repository and install dependencies
git clone https://github.com/wppconnect-team/wppconnect.git cd wppconnect npm install npm run prepare # installs Git hooks via Husky
- Configure environment variables (for authenticated tests)
• TEST_USER_ID – your WhatsApp user ID (e.g.,123456789@c.us
)
• TEST_GROUP_ID – optional group ID for group-scoped tests
Testing
Authenticated Test Wrapper (describeAuthenticatedTest
)
Provide end-to-end coverage by booting a logged-in session for Mocha tests.
import { describeAuthenticatedTest, testUserId, testGroupID } from './common';
describeAuthenticatedTest('Message sending', function(getClient) {
it('sends a text message', async function() {
const client = getClient();
const chatId = testUserId!;
const result = await client.sendText(chatId, 'Hello from tests!');
if (!result.ack) throw new Error('Message not acknowledged');
});
it('creates and sends to a new group', async function() {
if (!testGroupID) this.skip();
const client = getClient();
const { gid } = await client.createGroup('Test Group', [testUserId!]);
await client.sendText(gid._serialized, 'Hi group!');
});
});
Key Details
- Suite timeout: 60 s
- Session options:
session: 'test-authenticated'
disableWelcome: true
,updatesLog: false
,waitForLogin: false
- Hooks:
- Skips suite if
TEST_USER_ID
is unset - Suppresses logs (
wppconnect.defaultLogger.level = 'none'
) - Calls
client.waitForLogin()
, skips on failure - Closes session with
client.close()
after tests
- Skips suite if
Run all tests:
npm test
ESLint Configuration
WPPConnect enforces code quality with ESLint, TypeScript support, custom rules, overrides and npm scripts.
.eslintrc.js (core setup)
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'header', 'prettier'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended'
],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'header/header': ['error', 'block', [
'',
' * This file is part of WPPConnect.',
' * …license header lines…'
], 1],
'prettier/prettier': ['error', { endOfLine: 'auto' }]
},
overrides: [
{
files: ['src/lib/**/*.js'],
parser: '@babel/eslint-parser',
plugins: ['@babel'],
env: { amd: true, commonjs: true, browser: true },
rules: {
'@typescript-eslint/no-array-constructor': 'off',
'no-redeclare': 'off'
}
}
]
};
Linting Scripts (package.json)
{
"scripts": {
"lint:ts": "eslint -c .eslintrc.js --ext .ts src",
"lint:js": "eslint -c .eslintrc.js --ext .js src",
"lint": "npm run lint:ts && npm run lint:js"
}
}
• Run npm run lint
to check both TS and JS.
• Auto-fix issues: npx eslint --fix src/<file|dir>
.
Practical Usage
- Husky hooks install via
npm run prepare
. - Commitizen/Commitlint enforce message format.
- To add or override rules, update
rules
oroverrides
in.eslintrc.js
.
Commit Message Linting with Commitlint
Enforce Conventional Commits on every PR using Commitlint and GitHub Actions.
.commitlintrc.json
{
"$schema": "https://json.schemastore.org/commitlintrc.json",
"extends": ["@commitlint/config-conventional"],
"rules": {
"body-max-line-length": [0, "always", 100],
"header-max-length": [2, "always", 120],
"subject-case": [1, "never", [
"sentence-case",
"start-case",
"pascal-case",
"upper-case"
]]
}
}
GitHub Actions Workflow (.github/workflows/commitlint.yml)
name: commit lint
on: [pull_request]
jobs:
commitlint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Lint commit
uses: wagoid/commitlint-github-action@v5.5.1
with:
configFile: './.commitlintrc.json'
Usage
- Follow
<type>(<scope>): <subject>
(e.g.,feat(api): add user endpoint
). - Failures block merging on PRs.
Issue & Pull Request Templates
Enforce structured reporting and submissions using GitHub templates in .github/ISSUE_TEMPLATE/
and .github/PULL_REQUEST_TEMPLATE.md
.
Bug Report (.github/ISSUE_TEMPLATE/bug_report.md
)
---
name: Bug report
about: Create a report to help us improve
labels: 'bug, needs triage'
---
Sections: Description, Environment, Steps to Reproduce, Log Output, Your Code, Additional context.
Feature Request (.github/ISSUE_TEMPLATE/feature_request.md
)
---
name: Feature request
about: "I have a suggestion (and may want to implement it 😊)!"
labels: 'enhancement, needs triage'
---
Fields: Problem, Solution, Alternatives, Additional context.
Support Question (.github/ISSUE_TEMPLATE/support_question.md
)
---
name: Support Question
about: 'If you have a question, please check our Docs or Discord!'
labels: 'question, needs triage'
---
Checklist: searched issues/docs, code snippets, expected vs actual behavior.
Pull Request (.github/PULL_REQUEST_TEMPLATE.md
)
Fixes #<issue-number>
## Changes proposed in this pull request
- Bullet each change
To test:
```bash
npm install github:<username>/wppconnect#<branch>
Guidelines: reference issues (`Fixes #123`), summarize changes, include test instructions, follow CommonJS conventions.
### Submitting a Pull Request
1. Fork the repo and create a topic branch (`git checkout -b feat/my-feature`).
2. Implement your feature or fix.
3. Run tests and lint:
```bash
npm test
npm run lint
- Commit with Conventional Commits.
- Push and open a PR against
main
. - Ensure GitHub Actions pass all checks before request review.
By following these steps, you’ll maintain code quality, consistent style, and streamlined collaboration.