Skip to main content

Overview

This guide covers group operations including creation, participant management, and metadata updates using the whatsapp-rust library.

Accessing the groups API

All group operations are accessed through the groups() method:
use whatsapp_rust::client::Client;

let groups = client.groups();
See Groups API reference for the full API.

Creating groups

Basic group creation

use whatsapp_rust::features::groups::{GroupCreateOptions, GroupParticipantOptions};
use wacore_binary::jid::Jid;

let participant1: Jid = "1234567890@s.whatsapp.net".parse()?;
let participant2: Jid = "9876543210@s.whatsapp.net".parse()?;

let options = GroupCreateOptions {
    subject: "My New Group".to_string(),
    participants: vec![
        GroupParticipantOptions::new(participant1),
        GroupParticipantOptions::new(participant2),
    ],
    ..Default::default()
};

let result = client.groups().create_group(options).await?;
println!("Group created with JID: {}", result.gid);
See Groups API reference for full details.

Group creation options

use whatsapp_rust::features::groups::{
    MemberAddMode,
    MemberLinkMode,
    MembershipApprovalMode,
};

let options = GroupCreateOptions {
    subject: "Advanced Group".to_string(),
    participants: vec![
        GroupParticipantOptions::new(participant1),
    ],
    // Control who can add members
    member_add_mode: Some(MemberAddMode::AdminAdd),

    // Control membership approval requirement
    membership_approval_mode: Some(MembershipApprovalMode::On),

    // Control who can share invite links
    member_link_mode: Some(MemberLinkMode::AdminLink),

    // Create as a community parent group
    is_parent: false,

    // Whether the community requires approval to join (only used with is_parent)
    closed: false,

    // Allow non-admin members to create subgroups (only used with is_parent)
    allow_non_admin_sub_group_creation: false,

    // Create a general chat subgroup (only used with is_parent)
    create_general_chat: false,

    ..Default::default()
};

let result = client.groups().create_group(options).await?;
You can also use the builder pattern:
let options = GroupCreateOptions::builder()
    .subject("Advanced Group")
    .member_add_mode(MemberAddMode::AdminAdd)
    .membership_approval_mode(MembershipApprovalMode::On)
    .member_link_mode(MemberLinkMode::AdminLink)
    .build();
See Groups API reference for all options.

Group constraints

use whatsapp_rust::features::groups::*;

// GroupSubject: Max 100 characters (used for set_subject)
let subject = GroupSubject::new("A".repeat(101));
assert!(subject.is_err());  // Fails validation

// GroupDescription: Max 2048 characters (used for set_description)
let description = GroupDescription::new("B".repeat(2049));
assert!(description.is_err());  // Fails validation

// Participants: Max 257 (256 + creator)
// GROUP_SIZE_LIMIT = 257
GroupCreateOptions.subject is a plain String. Validated types GroupSubject and GroupDescription are used by set_subject and set_description respectively.

Querying group information

Get group metadata

let group_jid: Jid = "120363012345678@g.us".parse()?;

let metadata = client.groups().get_metadata(&group_jid).await?;

println!("Group: {}", metadata.subject);
println!("Participants: {}", metadata.participants.len());
println!("Addressing mode: {:?}", metadata.addressing_mode);

for participant in &metadata.participants {
    println!("- {} ({:?}, admin: {})",
        participant.jid,
        participant.participant_type,
        participant.is_admin(),
    );
    if participant.is_super_admin() {
        println!("  ^ Group creator");
    }
    if let Some(phone) = &participant.phone_number {
        println!("  Phone: {}", phone);
    }
}
See Groups API reference for metadata fields.

List all groups

use std::collections::HashMap;

let groups: HashMap<String, GroupMetadata> = client
    .groups()
    .get_participating()
    .await?;

for (jid, metadata) in groups {
    println!("{}: {} ({} members)",
        jid,
        metadata.subject,
        metadata.participants.len()
    );
}
See Groups API reference for details.

Query group info (internal)

For lower-level access with caching:
use wacore::client::context::GroupInfo;

let info: GroupInfo = client.groups().query_info(&group_jid).await?;

println!("Participants: {:?}", info.participants);
println!("Addressing mode: {:?}", info.addressing_mode);

    // Access LID-to-phone mapping if available
    let mapping = info.lid_to_pn_map();
    if !mapping.is_empty() {
        for (lid, pn) in mapping {
        println!("LID {} -> Phone {}", lid, pn);
    }
}
See Groups API reference for details.

Updating group metadata

Change group subject

let new_subject = GroupSubject::new("Updated Group Name")?;

client.groups().set_subject(&group_jid, new_subject).await?;
See Groups API reference for details.

Set or delete group description

use whatsapp_rust::features::groups::GroupDescription;

// Set description
let description = GroupDescription::new("This is the group description")?;
client.groups().set_description(
    &group_jid,
    Some(description),
    None,  // prev description ID (optional)
).await?;

// Delete description
client.groups().set_description(
    &group_jid,
    None,
    None,
).await?;
See Groups API reference for details.
The prev parameter can be used for conflict detection. If provided and the current description ID doesn’t match, the operation may fail. Pass None if you don’t need this check.

Managing participants

Add participants

let new_members = vec![
    "1111111111@s.whatsapp.net".parse()?,
    "2222222222@s.whatsapp.net".parse()?,
];

let responses = client.groups().add_participants(
    &group_jid,
    &new_members,
).await?;

// Check results
for response in responses {
    match response.status.as_deref() {
        Some("200") => println!("Added: {}", response.jid),
        Some("403") => println!("Failed to add {} (permission denied)", response.jid),
        Some("409") => println!("{} is already in the group", response.jid),
        other => println!("Error adding {}: {:?}", response.jid, other),
    }
}
See Groups API reference for response codes.

Remove participants

let members_to_remove = vec![
    "1111111111@s.whatsapp.net".parse()?,
];

let responses = client.groups().remove_participants(
    &group_jid,
    &members_to_remove,
).await?;

for response in responses {
    match response.status.as_deref() {
        Some("200") => println!("Removed: {}", response.jid),
        Some("404") => println!("{} is not in the group", response.jid),
        other => println!("Error removing {}: {:?}", response.jid, other),
    }
}
See Groups API reference for details.

Promote to admin

let participants = vec!["1234567890@s.whatsapp.net".parse()?];

client.groups().promote_participants(
    &group_jid,
    &participants,
).await?;

println!("Promoted to admin");
See Groups API reference for details.

Demote admin

let participants = vec!["1234567890@s.whatsapp.net".parse()?];

client.groups().demote_participants(
    &group_jid,
    &participants,
).await?;

println!("Demoted from admin");
See Groups API reference for details.

Leave group

client.groups().leave(&group_jid).await?;
println!("Left the group");
See Groups API reference for details.
// Get existing invite link
let link = client.groups().get_invite_link(&group_jid, false).await?;
println!("Invite link: https://chat.whatsapp.com/{}", link);

// Reset and get new invite link
let new_link = client.groups().get_invite_link(&group_jid, true).await?;
println!("New invite link: https://chat.whatsapp.com/{}", new_link);
See Groups API reference for details.
Resetting the invite link (reset = true) invalidates the old link. Anyone with the old link will no longer be able to join.

Join a group via invite

You can join a group using an invite code or a full https://chat.whatsapp.com/... URL. The method automatically strips the URL prefix if present.
use whatsapp_rust::features::groups::JoinGroupResult;

// Join using a full URL
let result = client.groups()
    .join_with_invite_code("https://chat.whatsapp.com/AbCdEfGh")
    .await?;

match &result {
    JoinGroupResult::Joined(jid) => {
        println!("Successfully joined group: {}", jid);
    }
    JoinGroupResult::PendingApproval(jid) => {
        println!("Request sent — waiting for admin approval in group: {}", jid);
    }
}
If the group has membership approval enabled, the result will be PendingApproval instead of Joined. See Groups API reference for details.

Accept a V4 invite message

V4 invites are sent directly by a group admin as a GroupInviteMessage, rather than shared as a URL. Use join_with_invite_v4 to accept these invites:
use whatsapp_rust::features::groups::JoinGroupResult;

let group_jid: Jid = "120363012345678@g.us".parse()?;
let admin_jid: Jid = "15551234567@s.whatsapp.net".parse()?;

let result = client.groups().join_with_invite_v4(
    &group_jid,
    "AbCdEfGh",       // invite code from the message
    1735689600,         // expiration timestamp (Unix seconds)
    &admin_jid,
).await?;

match &result {
    JoinGroupResult::Joined(jid) => {
        println!("Successfully joined group: {}", jid);
    }
    JoinGroupResult::PendingApproval(jid) => {
        println!("Request sent — waiting for admin approval in group: {}", jid);
    }
}
V4 invites have an expiration timestamp. The method automatically checks whether the invite has expired and returns an error if it has. Pass 0 as the expiration to skip the check.
See Groups API reference for details.

Preview a group before joining

Use get_invite_info to fetch group metadata from an invite code without joining:
let metadata = client.groups().get_invite_info("AbCdEfGh").await?;

println!("Group: {}", metadata.subject);
println!("Members: {}", metadata.participants.len());
println!("Requires approval: {}", metadata.membership_approval);
See Groups API reference for details.

Membership requests

When a group has membership approval enabled, new members must be approved by an admin. You can view, approve, and reject pending requests.

Get pending requests

let requests = client.groups().get_membership_requests(&group_jid).await?;

for request in &requests {
    println!("Pending: {} (requested at {:?})", request.jid, request.request_time);
}
See Groups API reference for details.

Approve or reject requests

// Approve specific requests
let to_approve = vec!["15551234567@s.whatsapp.net".parse()?];
let results = client.groups()
    .approve_membership_requests(&group_jid, &to_approve)
    .await?;

for result in &results {
    println!("Approved {}: status {:?}", result.jid, result.status);
}

// Reject specific requests
let to_reject = vec!["15559876543@s.whatsapp.net".parse()?];
client.groups()
    .reject_membership_requests(&group_jid, &to_reject)
    .await?;
See Groups API reference for details.
You must be a group admin to view, approve, or reject membership requests.

Cancel membership requests

Users can cancel their own pending membership requests:
let my_jid: Jid = "15551234567@s.whatsapp.net".parse()?;
let results = client.groups()
    .cancel_membership_requests(&group_jid, &[my_jid])
    .await?;
See Groups API reference for details.

Revoke invitation codes

Admins can revoke invitation codes from specific participants:
let to_revoke = vec!["15551234567@s.whatsapp.net".parse()?];
let results = client.groups()
    .revoke_request_code(&group_jid, &to_revoke)
    .await?;
See Groups API reference for details.

Batch operations

Batch query group info

Query metadata for multiple groups in a single request (up to 10,000 groups):
use whatsapp_rust::features::groups::BatchGroupResult;

let group_jids: Vec<Jid> = vec![
    "120363012345678@g.us".parse()?,
    "120363087654321@g.us".parse()?,
];

let results = client.groups().batch_get_info(group_jids).await?;

for result in results {
    match result {
        BatchGroupResult::Full(metadata) => {
            println!("Group: {} - {}", metadata.id, metadata.subject);
        }
        BatchGroupResult::Truncated { id, size } => {
            println!("Truncated info: {} (size: {:?})", id, size);
        }
        BatchGroupResult::Forbidden(id) => {
            println!("Access denied: {}", id);
        }
        BatchGroupResult::NotFound(id) => {
            println!("Not found: {}", id);
        }
    }
}
See Groups API reference for details.

Batch fetch profile pictures

Fetch profile pictures for multiple groups at once (up to 1,000 groups):
use whatsapp_rust::features::groups::{PictureType, GroupProfilePicture};

let group_jids: Vec<Jid> = vec![
    "120363012345678@g.us".parse()?,
    "120363087654321@g.us".parse()?,
];

let pictures = client.groups()
    .get_profile_pictures(group_jids, PictureType::Preview)
    .await?;

for pic in pictures {
    if let Some(url) = &pic.url {
        println!("Group {}: {}", pic.group_jid, url);
    }
}
See Groups API reference for details.

Group settings

Member add mode

Control who can add new members:
use whatsapp_rust::features::groups::MemberAddMode;

pub enum MemberAddMode {
    AdminAdd,      // Only admins can add members
    AllMemberAdd,  // All members can add others
}

// Set during creation
let options = GroupCreateOptions::builder()
    .subject("My Group")
    .member_add_mode(MemberAddMode::AdminAdd)
    .build();

// Or update an existing group
client.groups().set_member_add_mode(&group_jid, MemberAddMode::AdminAdd).await?;
See Groups API reference for details.

Membership approval mode

Require admin approval for new members:
use whatsapp_rust::features::groups::MembershipApprovalMode;

pub enum MembershipApprovalMode {
    On,   // Require approval
    Off,  // Auto-accept
}

// Set during creation
let options = GroupCreateOptions::builder()
    .subject("My Group")
    .membership_approval_mode(MembershipApprovalMode::On)
    .build();

// Or update an existing group
client.groups().set_membership_approval(&group_jid, MembershipApprovalMode::On).await?;
See Groups API reference for details.

Announcement mode

Toggle announcement mode on an existing group (only admins can send messages):
// Enable announcement mode
client.groups().set_announce(&group_jid, true).await?;

// Disable announcement mode
client.groups().set_announce(&group_jid, false).await?;
See Groups API reference for details.

Frequently-forwarded messages

Restrict or allow frequently-forwarded messages in the group:
// Restrict frequently-forwarded messages
client.groups().set_no_frequently_forwarded(&group_jid, true).await?;

// Allow frequently-forwarded messages
client.groups().set_no_frequently_forwarded(&group_jid, false).await?;
See Groups API reference for details.

Admin reports

Enable or disable admin reports in the group:
// Enable admin reports
client.groups().set_allow_admin_reports(&group_jid, true).await?;

// Disable admin reports
client.groups().set_allow_admin_reports(&group_jid, false).await?;
See Groups API reference for details.

Group history sharing

Enable or disable sharing group history with new members:
// Enable group history
client.groups().set_group_history(&group_jid, true).await?;

// Disable group history
client.groups().set_group_history(&group_jid, false).await?;
See Groups API reference for details. Control who can share invite links. This setting is updated via the MEX protocol:
use whatsapp_rust::features::groups::MemberLinkMode;

// Only admins can share invite links
client.groups().set_member_link_mode(&group_jid, MemberLinkMode::AdminLink).await?;

// All members can share invite links
client.groups().set_member_link_mode(&group_jid, MemberLinkMode::AllMemberLink).await?;
See Groups API reference for details.

Message history sharing mode

Control who can share message history with new members. This setting is updated via the MEX protocol:
use whatsapp_rust::features::groups::MemberShareHistoryMode;

// Only admins can share history
client.groups().set_member_share_history_mode(
    &group_jid,
    MemberShareHistoryMode::AdminShare,
).await?;

// All members can share history
client.groups().set_member_share_history_mode(
    &group_jid,
    MemberShareHistoryMode::AllMemberShare,
).await?;
See Groups API reference for details.

Limit sharing

Enable or disable limit sharing in the group. This setting is updated via the MEX protocol:
// Enable limit sharing
client.groups().set_limit_sharing(&group_jid, true).await?;

// Disable limit sharing
client.groups().set_limit_sharing(&group_jid, false).await?;
See Groups API reference for details.

Privacy tokens on group operations

When server-side A/B experiment flags are enabled, the library automatically attaches privacy tokens (tc_token) to participants during group creation and participant addition. This matches WhatsApp Web’s behavior and requires no changes to your code. The following AB prop flags control this behavior:
FlagEffect
PRIVACY_TOKEN_ON_GROUP_CREATEAttaches tokens to participants when creating a group
PRIVACY_TOKEN_ON_GROUP_PARTICIPANT_ADDAttaches tokens to participants when adding them to a group
PRIVACY_TOKEN_ONLY_CHECK_LIDOnly resolves tokens via LID mappings, skipping phone number fallback
These flags are fetched from the server automatically on each connection via fetch_props(). Token resolution uses the LID-to-phone-number cache and the tc_token store — if a valid, non-expired token exists for a participant, it is attached to the IQ request.
Privacy token attachment is fully automatic. You don’t need to call any extra methods or manage tokens manually. If the AB flags are disabled on the server, group operations work exactly as before.
See Client API - AB props cache for more details on experiment flags, and TC Token API for token management.

Addressing modes

Groups can use different addressing modes:
use wacore::types::message::AddressingMode;

pub enum AddressingMode {
    Pn,   // Phone number (legacy)
    Lid,  // Long ID (new privacy mode)
}

// Check the group's addressing mode
let info = client.groups().query_info(&group_jid).await?;
match info.addressing_mode {
    AddressingMode::Pn => println!("Group uses phone numbers"),
    AddressingMode::Lid => println!("Group uses LIDs (privacy mode)"),
}
LID (Long ID) mode provides better privacy by hiding phone numbers. The library automatically handles LID-to-phone-number mapping when needed.

Participant response codes

When adding or removing participants, check the response codes:
use whatsapp_rust::features::groups::ParticipantChangeResponse;

let responses = client.groups().add_participants(&group_jid, &participants).await?;

for response in responses {
    match response.status.as_deref() {
        Some("200") => println!("Success: {}", response.jid),
        Some("403") => println!("Permission denied: {}", response.jid),
        Some("404") => println!("Not found: {}", response.jid),
        Some("409") => println!("Already in group: {}", response.jid),
        other => println!("Unknown status for {}: {:?}", response.jid, other),
    }
    
    if let Some(error) = &response.error {
        println!("Error: {}", error);
    }
}

Error handling

use anyhow::Result;

async fn create_group_safely(
    client: &Client,
    subject: &str,
    participants: Vec<Jid>,
) -> Result<Jid> {
    let options = GroupCreateOptions::builder()
        .subject(subject)
        .participants(
            participants
                .into_iter()
                .map(GroupParticipantOptions::new)
                .collect(),
        )
        .build();
    
    match client.groups().create_group(options).await {
        Ok(result) => {
            println!("✅ Group created: {}", result.gid);
            Ok(result.gid)
        }
        Err(e) => {
            eprintln!("❌ Failed to create group: {:?}", e);
            Err(e)
        }
    }
}

Advanced usage

Handling LID groups

For LID-based groups, you may need phone number mappings:
let info = client.groups().query_info(&group_jid).await?;

if info.addressing_mode == AddressingMode::Lid {
    // Get LID-to-phone mapping
    let mapping = info.lid_to_pn_map();
    for (lid, pn_jid) in mapping {
        println!("LID {} maps to phone {}", lid, pn_jid);
    }
}

Participant options

For advanced participant configurations:
use whatsapp_rust::features::groups::GroupParticipantOptions;
use wacore_binary::jid::Jid;

let lid_jid: Jid = "123456789012345:10@lid".parse()?;
let phone_jid: Jid = "1234567890@s.whatsapp.net".parse()?;

let participant = GroupParticipantOptions::new(lid_jid)
    .with_phone_number(phone_jid);

// The library will automatically resolve phone numbers for LID participants
See Groups API reference for addressing mode details.

Best practices

1
Validate input before creating groups
2
// ✅ Good: Validate before creation
let subject = match GroupSubject::new(user_input) {
    Ok(s) => s,
    Err(e) => {
        eprintln!("Invalid subject: {}", e);
        return Err(e);
    }
};

// ❌ Bad: No validation
let subject = GroupSubject::new(user_input)?;  // May panic on invalid input
3
Check response codes
4
Always check participant operation responses:
5
let responses = client.groups().add_participants(&group_jid, &participants).await?;

let mut success_count = 0;
let mut failed = Vec::new();

for response in responses {
    if response.status.as_deref() == Some("200") {
        success_count += 1;
    } else {
        failed.push(response);
    }
}

println!("Added {} participants", success_count);
if !failed.is_empty() {
    eprintln!("Failed to add {} participants", failed.len());
}
6
Handle addressing modes
7
Be aware of LID vs PN addressing:
8
let info = client.groups().query_info(&group_jid).await?;

if info.addressing_mode == AddressingMode::Lid {
    // Handle LID-based group
    // Phone numbers may not be directly visible
} else {
    // Handle phone-number-based group
}
9
Cache group information
10
The library automatically caches group info:
11
// First call: fetches from server
let info = client.groups().query_info(&group_jid).await?;

// Second call: uses cache
let info = client.groups().query_info(&group_jid).await?;

Next steps