Skip to main content
Baileys is designed with extensibility in mind. Instead of forking the project and modifying its internals, you can add custom functionality by hooking into the WhatsApp protocol directly.

Why extend instead of fork?

Extending Baileys provides several advantages:
  • Easier updates - Pull in new Baileys versions without merge conflicts
  • Maintainability - Keep your custom code separate from the core library
  • Modularity - Build reusable extensions that can be shared
  • Stability - Avoid breaking core functionality

Understanding the WhatsApp protocol

To build custom functionality, you need to understand how WhatsApp communicates with clients.
If you want to learn the WhatsApp protocol in depth, study:
  • Libsignal Protocol - End-to-end encryption framework
  • Noise Protocol - Cryptographic handshake patterns

Binary node structure

WhatsApp messages use a binary format called BinaryNode. Each node has three components:
interface BinaryNode {
  tag: string        // Message type (e.g., 'message', 'iq', 'ib')
  attrs: Object      // Metadata as key-value pairs
  content: any       // Actual message data or nested nodes
}

Enable debug logging

Before building custom functionality, enable debug logging to see all WhatsApp protocol messages:
import P from 'pino'
import makeWASocket from '@whiskeysockets/baileys'

const sock = makeWASocket({
  logger: P({ level: 'debug' }),
})
Setting the log level to 'debug' will show all unhandled messages from WhatsApp in your console, helping you discover new protocol features.

Example: Track phone battery

Let’s build a feature to track your phone’s battery percentage.

Step 1: Enable debug logging

First, enable debug logging and observe the console:
const sock = makeWASocket({
  logger: P({ level: 'debug' }),
})

Step 2: Identify the message

You’ll see messages like this in the console when your phone’s battery changes:
{
  "level": 10,
  "fromMe": false,
  "frame": {
    "tag": "ib",
    "attrs": {
      "from": "@s.whatsapp.net"
    },
    "content": [
      {
        "tag": "edge_routing",
        "attrs": {},
        "content": [
          {
            "tag": "routing_info",
            "attrs": {},
            "content": {
              "type": "Buffer",
              "data": [8,2,8,5]
            }
          }
        ]
      }
    ]
  },
  "msg": "communication"
}

Step 3: Register a WebSocket callback

Use the sock.ws.on() method to listen for specific messages. See WebSocket events for details.
// Listen for edge_routing messages
sock.ws.on('CB:edge_routing', (node: BinaryNode) => {
  console.log('Received edge_routing message:', node)
  // Process the battery data
})

Example: Custom message handler

Here’s a more practical example - adding a custom handler for a specific message type:
import makeWASocket, { BinaryNode } from '@whiskeysockets/baileys'
import P from 'pino'

const sock = makeWASocket({
  logger: P({ level: 'debug' }),
})

// Custom handler for presence updates
sock.ws.on('CB:presence', (node: BinaryNode) => {
  const from = node.attrs.from
  const type = node.attrs.type
  
  console.log(`${from} is ${type}`)
  
  // Custom logic
  if (type === 'available') {
    console.log(`${from} came online!`)
  } else if (type === 'unavailable') {
    console.log(`${from} went offline!`)
  }
})

Building reusable extensions

Create modular extensions that can be easily shared:
// battery-tracker.ts
import { BinaryNode } from '@whiskeysockets/baileys'

export function enableBatteryTracking(sock: any) {
  sock.ws.on('CB:ib,,battery', (node: BinaryNode) => {
    const batteryNode = node.content.find(
      (n: any) => n.tag === 'battery'
    )
    
    if (batteryNode) {
      const level = batteryNode.attrs.value
      console.log(`Phone battery: ${level}%`)
      
      // Emit custom event
      sock.ev.emit('battery.update', { level: parseInt(level) })
    }
  })
  
  return sock
}
Then use it in your application:
import makeWASocket from '@whiskeysockets/baileys'
import { enableBatteryTracking } from './battery-tracker'

const sock = makeWASocket({ /* config */ })
enableBatteryTracking(sock)

// Listen for battery updates
sock.ev.on('battery.update', ({ level }) => {
  console.log(`Battery level: ${level}%`)
  
  if (level < 20) {
    console.warn('Phone battery is low!')
  }
})

Exploring protocol messages

Using trace logging

For even more detailed logs, use 'trace' level:
import P from 'pino'

const logger = P({
  level: 'trace',
  transport: {
    target: 'pino-pretty',
    options: { colorize: true }
  }
})

const sock = makeWASocket({ logger })

Understanding message flow

Examine the onMessageReceived function in socket.ts to understand how WebSocket events are fired and processed.
The message processing flow:
  1. Receive - Raw data arrives via WebSocket
  2. Decode - Noise protocol decrypts the frame
  3. Parse - Binary data converts to BinaryNode
  4. Emit - Events fire based on tag and attributes
  5. Handle - Your callbacks process the message

Best practices

Register callbacks for specific message patterns rather than catching everything:
// Good - specific
sock.ws.on('CB:presence,type:available', callback)

// Avoid - too broad
sock.ws.on('CB:presence', callback)
Always wrap custom handlers in try-catch blocks:
sock.ws.on('CB:message', (node: BinaryNode) => {
  try {
    // Your custom logic
  } catch (error) {
    console.error('Error in custom handler:', error)
  }
})
Remove event listeners when they’re no longer needed:
const handler = (node: BinaryNode) => {
  // Handle message
}

sock.ws.on('CB:message', handler)

// Later, when done:
sock.ws.off('CB:message', handler)
When you discover new protocol features, document them for the community:
/**
 * Battery status message structure:
 * - tag: 'ib'
 * - content[0].tag: 'battery'
 * - content[0].attrs.value: percentage (0-100)
 * - content[0].attrs.live: 'true' if charging
 */

Debugging tips

import { binaryNodeToString } from '@whiskeysockets/baileys'

sock.ws.on('CB:ib', (node: BinaryNode) => {
  // Convert to readable XML-like format
  console.log(binaryNodeToString(node))
})

Next steps

Build docs developers (and LLMs) love