Skip to content
MessageBuilder
Documentation menu
Fluent chaining

Fluent chaining

How MessageBuilder setters and adders work, when call order matters, and which pitfalls to avoid.

Concepts
v4.8/Baileys runtime
On this page

Every builder follows the same pattern: methods mutate internal state and return this. That makes a message read like a sentence.

await new Button(sock)
  .setTitle('Order')
  .setBody('Order #1042 is being prepared.')
  .addReply('Track', 'track')
  .addCopy('Copy code', 'MB-1042')
  .send(jid);

Setters vs adders

Kind Examples Behavior
Setter setBody, setFooter, setImage Overwrites the previous value.
Adder addReply, addButton, addText Appends an item to a list.
Builder build, toCard, send Produces a payload or sends it.
Utility getIds, hasId, peek Reads state without mutating it.

Setters can be called repeatedly; the last value wins. Adders accumulate until you call clearButtons() or delete a node.

Call order

For most methods, order does not matter:

// Same result
button.setBody('Hello').addReply('OK', 'ok');
button.addReply('OK', 'ok').setBody('Hello');

Lists are the important exception:

button
  .addSelection('Open menu')   // makes the selection active
  .makeSection('Food')         // makes the section active
  .makeRow('Fried rice', '$8', '', 'rice')
  .makeRow('Chicken noodles', '$7', '', 'noodles');

makeSection() needs an active selection. makeRow() needs an active section. Otherwise the library throws an Error.

One builder, one message

Builders keep state. Reusing the same instance for two recipients carries the first message’s buttons into the second.

// Wrong
const button = new Button(sock).setBody('Hello').addReply('OK', 'ok');
await button.send(jidA);
await button.send(jidB); // still carries the same buttons

// Correct
const make = () => new Button(sock).setBody('Hello').addReply('OK', 'ok');
await make().send(jidA);
await make().send(jidB);

If you need to reuse an instance, call clearButtons() and explicitly reset the other fields.

Modifying an existing message

Every builder has loadFrom(msg), which reads an old payload and restores the builder state:

const rebuilt = new Button(sock).loadFrom(receivedMessage);
rebuilt.setBody('New body');

This is useful for replying to an interactive message or resending with small changes. AIRich also supports loadFrom() for sections and submessages.

Injecting raw fields

If WhatsApp has a field without a dedicated method, use addPayload():

new Button(sock)
  .setBody('Hello')
  .addReply('OK', 'ok')
  .addPayload({
    messageContextInfo: {
      deviceListMetadata: {},
      deviceListMetadataVersion: 2,
    },
  });

Fields are merged into the root payload. Be careful: a field with the same name can overwrite structure created by the library.

Chainable, not immutable

Methods return the same instance, not a copy. This matters when you keep a reference:

const base = new Button(sock).setBody('Hello');
const a = base.addReply('A', 'a'); // a === base

Create a new instance for variations, or carefully clone the raw state (methods and the socket cannot be cloned).