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.
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.
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.
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.
Group invite links
Get invite link
// 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.
Member link mode
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:
| Flag | Effect |
|---|
PRIVACY_TOKEN_ON_GROUP_CREATE | Attaches tokens to participants when creating a group |
PRIVACY_TOKEN_ON_GROUP_PARTICIPANT_ADD | Attaches tokens to participants when adding them to a group |
PRIVACY_TOKEN_ONLY_CHECK_LID | Only 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
// ✅ 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
Always check participant operation responses:
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());
}
Be aware of LID vs PN addressing:
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
}
The library automatically caches group info:
// 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