> ## Documentation Index
> Fetch the complete documentation index at: https://personal-9eca1d6c-claude-eager-cerf-0xyrbp.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Groups

> Group management operations - create, modify, and manage WhatsApp groups

The `Groups` struct provides methods for managing WhatsApp groups, including creating groups, managing participants, and modifying group settings.

## Access

Access group operations through the client:

```rust theme={null}
let groups = client.groups();
```

## Methods

### query\_info

Query group information with caching support.

```rust theme={null}
pub async fn query_info(&self, jid: &Jid) -> Result<Arc<GroupInfo>, anyhow::Error>
```

**Parameters:**

* `jid` - Group JID (must end with `@g.us`)

**Returns:**

* `Arc<GroupInfo>` - Shared, reference-counted snapshot containing the participants list and addressing mode. Repeated calls for the same group return the same `Arc` from the in-memory cache, so warm sends avoid deep-cloning group metadata.

**Example:**

```rust theme={null}
use std::sync::Arc;
use wacore::client::context::GroupInfo;

let group_jid: Jid = "123456789@g.us".parse()?;
let info: Arc<GroupInfo> = client.groups().query_info(&group_jid).await?;

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

// Cheaply share the snapshot across tasks without copying participants.
let info_for_task = info.clone();
tokio::spawn(async move {
    println!("Participants in task: {}", info_for_task.participants.len());
});
```

### get\_participating

Get all groups the client is participating in.

```rust theme={null}
pub async fn get_participating(&self) -> Result<HashMap<Jid, GroupMetadata>, anyhow::Error>
```

**Returns:**

* `HashMap<Jid, GroupMetadata>` - Map of group `Jid` to metadata

<Note>
  This map is keyed by `Jid`. Call `.to_string()` on the key if you need the string form.
</Note>

`GroupMetadata` implements `PartialEq` and `Eq`, allowing direct comparison of group metadata instances.

**GroupMetadata fields:**

* `id: Jid` - Group JID
* `subject: String` - Group name
* `participants: Vec<GroupParticipant>` - List of participants
* `addressing_mode: AddressingMode` - Phone number or LID mode
* `creator: Option<Jid>` - Group creator JID
* `creation_time: Option<u64>` - Group creation timestamp (Unix seconds)
* `subject_time: Option<u64>` - Subject modification timestamp (Unix seconds)
* `subject_owner: Option<Jid>` - Subject owner JID
* `description: Option<String>` - Group description body text
* `description_id: Option<String>` - Description ID (for conflict detection)
* `description_owner: Option<Jid>` - JID of the participant who set the description
* `description_time: Option<u64>` - Timestamp when the description was set (Unix seconds)
* `is_locked: bool` - Whether only admins can edit group info
* `is_announcement: bool` - Whether only admins can send messages
* `ephemeral_expiration: u32` - Disappearing messages timer in seconds (0 = disabled)
* `ephemeral_trigger: Option<u32>` - Disappearing mode trigger value (from the `trigger` attribute on `<ephemeral>`)
* `membership_approval: bool` - Whether admin approval is required to join
* `member_add_mode: Option<MemberAddMode>` - Who can add members
* `member_link_mode: Option<MemberLinkMode>` - Who can use invite links
* `size: Option<u32>` - Total participant count
* `is_parent_group: bool` - Whether this group is a community parent group
* `parent_group_jid: Option<Jid>` - JID of the parent community (for subgroups)
* `is_default_sub_group: bool` - Whether this is the default announcement subgroup of a community
* `is_general_chat: bool` - Whether this is the general chat subgroup of a community
* `allow_non_admin_sub_group_creation: bool` - Whether non-admin community members can create subgroups
* `no_frequently_forwarded: bool` - Whether frequently-forwarded messages are restricted
* `member_share_history_mode: Option<MemberShareHistoryMode>` - Who can share message history with new members
* `growth_locked: Option<GrowthLockInfo>` - Growth lock status (invite links temporarily disabled by the system)
* `is_suspended: bool` - Whether the group is suspended
* `allow_admin_reports: bool` - Whether admin reports are allowed
* `is_hidden_group: bool` - Whether the group is hidden
* `is_incognito: bool` - Whether incognito mode is enabled
* `has_group_history: bool` - Whether group history is enabled
* `is_limit_sharing_enabled: bool` - Whether limit sharing is enabled

See [Community API](/api/community) for community-specific operations.

`GroupParticipant` implements `PartialEq` and `Eq`.

**GroupParticipant fields:**

* `jid: Jid` - Participant JID
* `phone_number: Option<Jid>` - Phone number JID (for LID groups)
* `participant_type: ParticipantType` - Participant role (member, admin, or super admin)

**Example:**

```rust theme={null}
let groups = client.groups().get_participating().await?;

for (jid, metadata) in groups {
    println!("Group: {} ({})", metadata.subject, jid);
    println!("  Participants: {}", metadata.participants.len());
    
    for participant in &metadata.participants {
        println!("    {} ({:?})", participant.jid, participant.participant_type);
    }
}
```

### get\_metadata

Get metadata for a specific group.

```rust theme={null}
pub async fn get_metadata(&self, jid: &Jid) -> Result<GroupMetadata, anyhow::Error>
```

**Parameters:**

* `jid` - Group JID

**Returns:**

* `GroupMetadata` - Group metadata (see `get_participating` for fields)

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;
let metadata = client.groups().get_metadata(&group_jid).await?;

println!("Subject: {}", metadata.subject);
println!("Mode: {:?}", metadata.addressing_mode);
```

### create\_group

Create a new group.

```rust theme={null}
pub async fn create_group(
    &self,
    options: GroupCreateOptions,
) -> Result<CreateGroupResult, anyhow::Error>
```

**Parameters:**

* `options: GroupCreateOptions` - Group creation options
  * `subject: String` - Group name (max 100 characters)
  * `participants: Vec<GroupParticipantOptions>` - Initial participants
  * `member_link_mode: Option<MemberLinkMode>` - Who can use invite links (default: `AdminLink`)
  * `member_add_mode: Option<MemberAddMode>` - Who can add members (default: `AllMemberAdd`)
  * `membership_approval_mode: Option<MembershipApprovalMode>` - Require admin approval (default: `Off`)
  * `ephemeral_expiration: Option<u32>` - Disappearing messages timer in seconds (default: `0`)
  * `is_parent: bool` - Create as a community parent group (default: `false`)
  * `closed: bool` - Whether the community requires approval to join. Only used when `is_parent` is `true` (default: `false`)
  * `allow_non_admin_sub_group_creation: bool` - Allow non-admin members to create subgroups. Only used when `is_parent` is `true` (default: `false`)
  * `create_general_chat: bool` - Create a general chat subgroup alongside the community. Only used when `is_parent` is `true` (default: `false`)
  * `linked_parent: Option<Jid>` - Atomically link this group as a subgroup of an existing community on create. Mutually exclusive with `is_parent` — when set, the new group is always classified as a subgroup even if `is_parent: true` is also passed. (added in v0.6)
  * `description: Option<String>` - Inline group description, emitted as a `<description>` child of the `<create>` stanza so it lands in one round-trip. Matches the existing community-create behavior. (added in v0.6)

**Returns:**

* `CreateGroupResult` with a `metadata: GroupMetadata` field carrying the full server response (JID, subject, addressing mode, participants with display names, parent/community linkage, etc.).

When the `PRIVACY_TOKEN_ON_GROUP_CREATE` AB prop is enabled, the library automatically resolves and attaches privacy tokens (`tc_token`) to each participant during group creation. This is handled internally — you don't need to manage tokens yourself.

**Example:**

```rust theme={null}
use whatsapp_rust::features::groups::{GroupCreateOptions, GroupParticipantOptions};

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

let options = GroupCreateOptions::builder()
    .subject("My New Group")
    .participants(vec![
        GroupParticipantOptions::new(participant1),
        GroupParticipantOptions::new(participant2),
    ])
    .build();

let result = client.groups().create_group(options).await?;
println!("Created group: {} ({})", result.metadata.subject, result.metadata.id);
for participant in &result.metadata.participants {
    println!("- {} ({:?})", participant.jid, participant.participant_type);
}
```

<Note>
  Since v0.6, `create_group` returns the full `GroupMetadata` (matching `get_metadata`) instead of just the JID. Inspect `result.metadata` for participants, addressing mode, ephemeral timer, and parent linkage in a single round-trip. `GroupMetadata.participants` is a `Vec<GroupParticipant>` (the IQ-response shape), not the `GroupParticipantInfo` used by group notification events — so masked-number `display_name` labels are only available on the event-side `GroupParticipantInfo`, not here.
</Note>

### set\_subject

Change the group name.

```rust theme={null}
pub async fn set_subject(&self, jid: &Jid, subject: GroupSubject) -> Result<(), anyhow::Error>
```

**Parameters:**

* `jid` - Group JID
* `subject` - New group name (max 100 characters)

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;
let new_subject = GroupSubject::new("Updated Group Name")?;

client.groups().set_subject(&group_jid, new_subject).await?;
```

### set\_description

Set or delete the group description.

```rust theme={null}
pub async fn set_description(
    &self,
    jid: &Jid,
    description: Option<GroupDescription>,
    prev: Option<&str>,
) -> Result<(), anyhow::Error>
```

**Parameters:**

* `jid` - Group JID
* `description` - New description (max 2048 characters) or `None` to delete
* `prev` - Current description ID for conflict detection, borrowed as `Option<&str>` (pass `None` if unknown)

<Note>
  `prev` borrows the id (`Option<&str>`). Pass `Some("PREV_ID")` or `None`.
</Note>

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;
let desc = GroupDescription::new("This is our group chat")?;

client.groups().set_description(&group_jid, Some(desc), None).await?;

// Replace an existing description, passing the previous description id for conflict detection
let updated = GroupDescription::new("Updated topic")?;
client.groups().set_description(&group_jid, Some(updated), Some("PREV_ID")).await?;

// Delete description
client.groups().set_description(&group_jid, None, None).await?;
```

### leave

Leave a group.

```rust theme={null}
pub async fn leave(&self, jid: &Jid) -> Result<(), anyhow::Error>
```

**Parameters:**

* `jid` - Group JID to leave

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;
client.groups().leave(&group_jid).await?;
```

### add\_participants

Add participants to a group.

```rust theme={null}
pub async fn add_participants(
    &self,
    jid: &Jid,
    participants: &[Jid],
) -> Result<Vec<ParticipantChangeResponse>, anyhow::Error>
```

**Parameters:**

* `jid` - Group JID
* `participants` - Array of participant JIDs to add

**Returns:**

* `Vec<ParticipantChangeResponse>` - Result for each participant

When the `PRIVACY_TOKEN_ON_GROUP_PARTICIPANT_ADD` AB prop is enabled, the library automatically resolves and attaches privacy tokens (`tc_token`) to each participant. This is handled internally — you don't need to manage tokens yourself.

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;
let new_members = vec![
    "15551234567@s.whatsapp.net".parse()?,
    "15559876543@s.whatsapp.net".parse()?,
];

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

for result in results {
    println!("Added: {:?}", result);
}
```

### remove\_participants

Remove participants from a group.

```rust theme={null}
pub async fn remove_participants(
    &self,
    jid: &Jid,
    participants: &[Jid],
) -> Result<Vec<ParticipantChangeResponse>, anyhow::Error>
```

**Parameters:**

* `jid` - Group JID
* `participants` - Array of participant JIDs to remove

**Returns:**

* `Vec<ParticipantChangeResponse>` - Result for each participant

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;
let to_remove = vec!["15551234567@s.whatsapp.net".parse()?];

client.groups().remove_participants(&group_jid, &to_remove).await?;
```

### promote\_participants

Promote participants to admin.

```rust theme={null}
pub async fn promote_participants(
    &self,
    jid: &Jid,
    participants: &[Jid],
) -> Result<(), anyhow::Error>
```

**Parameters:**

* `jid` - Group JID
* `participants` - Array of participant JIDs to promote

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;
let to_promote = vec!["15551234567@s.whatsapp.net".parse()?];

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

### demote\_participants

Demote admin participants to regular members.

```rust theme={null}
pub async fn demote_participants(
    &self,
    jid: &Jid,
    participants: &[Jid],
) -> Result<(), anyhow::Error>
```

**Parameters:**

* `jid` - Group JID
* `participants` - Array of admin JIDs to demote

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;
let to_demote = vec!["15551234567@s.whatsapp.net".parse()?];

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

### get\_invite\_link

Get or reset the group invite link.

```rust theme={null}
pub async fn get_invite_link(&self, jid: &Jid, reset: bool) -> Result<String, anyhow::Error>
```

**Parameters:**

* `jid` - Group JID
* `reset` - Whether to reset and generate a new invite link

**Returns:**

* `String` - Invite link code

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;

// Get current 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 link
let new_link = client.groups().get_invite_link(&group_jid, true).await?;
println!("New invite link: https://chat.whatsapp.com/{}", new_link);
```

### join\_with\_invite\_code

Join a group using an invite code or full invite URL.

```rust theme={null}
pub async fn join_with_invite_code(&self, code: &str) -> Result<JoinGroupResult, anyhow::Error>
```

**Parameters:**

* `code` - Invite code or full URL (e.g., `"AbCdEfGh"` or `"https://chat.whatsapp.com/AbCdEfGh"`)

**Returns:**

* `JoinGroupResult` - Either `Joined(Jid)` if immediately joined, or `PendingApproval(Jid)` if the group requires admin approval

**Example:**

```rust theme={null}
use whatsapp_rust::features::groups::JoinGroupResult;

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

match &result {
    JoinGroupResult::Joined(jid) => println!("Joined group: {}", jid),
    JoinGroupResult::PendingApproval(jid) => println!("Pending approval for group: {}", jid),
}

// Access the group JID regardless of result
println!("Group JID: {}", result.group_jid());
```

### join\_with\_invite\_v4

Accept a V4 group invite received as a `GroupInviteMessage` (not a link). V4 invites are sent directly by a group admin as a message, rather than shared as a URL.

```rust theme={null}
pub async fn join_with_invite_v4(
    &self,
    group_jid: &Jid,
    code: &str,
    expiration: i64,
    admin_jid: &Jid,
) -> Result<JoinGroupResult, anyhow::Error>
```

**Parameters:**

* `group_jid` - The target group JID
* `code` - Invite code from the `GroupInviteMessage`
* `expiration` - Invite expiration timestamp (Unix seconds). The method returns an error if the invite has expired.
* `admin_jid` - JID of the admin who sent the invite

**Returns:**

* `JoinGroupResult` - Either `Joined(Jid)` if immediately joined, or `PendingApproval(Jid)` if the group requires admin approval

**Example:**

```rust theme={null}
use whatsapp_rust::features::groups::JoinGroupResult;

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

let result = client.groups().join_with_invite_v4(
    &group_jid,
    "AbCdEfGh",
    1735689600,    // expiration timestamp
    &admin_jid,
).await?;

match &result {
    JoinGroupResult::Joined(jid) => println!("Joined group: {}", jid),
    JoinGroupResult::PendingApproval(jid) => println!("Pending approval for group: {}", jid),
}
```

<Note>
  V4 invites expire. The method automatically checks the expiration timestamp and returns an error if the invite has already expired. You can pass `0` as the expiration to skip the expiration check.
</Note>

### get\_invite\_info

Get group metadata from an invite code without joining the group.

```rust theme={null}
pub async fn get_invite_info(&self, code: &str) -> Result<GroupMetadata, anyhow::Error>
```

**Parameters:**

* `code` - Invite code or full URL

**Returns:**

* `GroupMetadata` - Group metadata (see `get_participating` for fields)

**Example:**

```rust theme={null}
// Preview a group before joining
let metadata = client.groups().get_invite_info("AbCdEfGh").await?;

println!("Group: {}", metadata.subject);
println!("Participants: {}", metadata.participants.len());
println!("Approval required: {}", metadata.membership_approval);
```

### set\_locked

Lock or unlock the group so only admins can change group info.

```rust theme={null}
pub async fn set_locked(&self, jid: &Jid, locked: bool) -> Result<(), anyhow::Error>
```

**Parameters:**

* `jid` - Group JID
* `locked` - `true` to lock (only admins edit info), `false` to unlock

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;

// Lock group info
client.groups().set_locked(&group_jid, true).await?;

// Unlock group info
client.groups().set_locked(&group_jid, false).await?;
```

### set\_announce

Set announcement mode. When enabled, only admins can send messages.

```rust theme={null}
pub async fn set_announce(&self, jid: &Jid, announce: bool) -> Result<(), anyhow::Error>
```

**Parameters:**

* `jid` - Group JID
* `announce` - `true` to enable (only admins send), `false` to disable

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;

// Enable announcement mode
client.groups().set_announce(&group_jid, true).await?;

// Disable announcement mode
client.groups().set_announce(&group_jid, false).await?;
```

### set\_ephemeral

Set the disappearing messages timer on the group.

```rust theme={null}
pub async fn set_ephemeral(&self, jid: &Jid, expiration: u32) -> Result<(), anyhow::Error>
```

**Parameters:**

* `jid` - Group JID
* `expiration` - Timer duration in seconds. Common values: `86400` (24 hours), `604800` (7 days), `7776000` (90 days). Pass `0` to disable.

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;

// Enable 7-day disappearing messages
client.groups().set_ephemeral(&group_jid, 604800).await?;

// Disable disappearing messages
client.groups().set_ephemeral(&group_jid, 0).await?;
```

### set\_membership\_approval

Set membership approval mode. When enabled, new members must be approved by an admin.

```rust theme={null}
pub async fn set_membership_approval(
    &self,
    jid: &Jid,
    mode: MembershipApprovalMode,
) -> Result<(), anyhow::Error>
```

**Parameters:**

* `jid` - Group JID
* `mode` - `MembershipApprovalMode::On` to require approval, `MembershipApprovalMode::Off` to disable

**Example:**

```rust theme={null}
use whatsapp_rust::features::groups::MembershipApprovalMode;

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

// Require admin approval
client.groups().set_membership_approval(&group_jid, MembershipApprovalMode::On).await?;

// Remove approval requirement
client.groups().set_membership_approval(&group_jid, MembershipApprovalMode::Off).await?;
```

### get\_membership\_requests

Get pending membership approval requests for a group.

```rust theme={null}
pub async fn get_membership_requests(
    &self,
    jid: &Jid,
) -> Result<Vec<MembershipRequest>, anyhow::Error>
```

**Parameters:**

* `jid` - Group JID

**Returns:**

* `Vec<MembershipRequest>` - List of pending requests

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;
let requests = client.groups().get_membership_requests(&group_jid).await?;

for request in &requests {
    println!("Pending request from: {}", request.jid);
    if let Some(time) = request.request_time {
        println!("  Requested at: {}", time);
    }
}
```

### approve\_membership\_requests

Approve pending membership requests for a group.

```rust theme={null}
pub async fn approve_membership_requests(
    &self,
    jid: &Jid,
    participants: &[Jid],
) -> Result<Vec<ParticipantChangeResponse>, anyhow::Error>
```

**Parameters:**

* `jid` - Group JID
* `participants` - Array of JIDs to approve

**Returns:**

* `Vec<ParticipantChangeResponse>` - Result for each participant

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;

// Get pending requests
let requests = client.groups().get_membership_requests(&group_jid).await?;
let jids: Vec<Jid> = requests.iter().map(|r| r.jid.clone()).collect();

// Approve all pending requests
let results = client.groups().approve_membership_requests(&group_jid, &jids).await?;

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

### reject\_membership\_requests

Reject pending membership requests for a group.

```rust theme={null}
pub async fn reject_membership_requests(
    &self,
    jid: &Jid,
    participants: &[Jid],
) -> Result<Vec<ParticipantChangeResponse>, anyhow::Error>
```

**Parameters:**

* `jid` - Group JID
* `participants` - Array of JIDs to reject

**Returns:**

* `Vec<ParticipantChangeResponse>` - Result for each participant

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;
let to_reject = vec!["15551234567@s.whatsapp.net".parse()?];

let results = client.groups().reject_membership_requests(&group_jid, &to_reject).await?;
```

### set\_member\_add\_mode

Set who can add members to the group.

```rust theme={null}
pub async fn set_member_add_mode(
    &self,
    jid: &Jid,
    mode: MemberAddMode,
) -> Result<(), anyhow::Error>
```

**Parameters:**

* `jid` - Group JID
* `mode` - `MemberAddMode::AdminAdd` to restrict to admins, `MemberAddMode::AllMemberAdd` to allow all members

**Example:**

```rust theme={null}
use whatsapp_rust::features::groups::MemberAddMode;

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

// Only admins can add members
client.groups().set_member_add_mode(&group_jid, MemberAddMode::AdminAdd).await?;

// All members can add others
client.groups().set_member_add_mode(&group_jid, MemberAddMode::AllMemberAdd).await?;
```

### set\_no\_frequently\_forwarded

Restrict or allow frequently-forwarded messages in the group.

```rust theme={null}
pub async fn set_no_frequently_forwarded(
    &self,
    jid: &Jid,
    restrict: bool,
) -> Result<(), anyhow::Error>
```

**Parameters:**

* `jid` - Group JID
* `restrict` - `true` to restrict frequently-forwarded messages, `false` to allow them

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;

// 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?;
```

### set\_allow\_admin\_reports

Enable or disable admin reports in the group.

```rust theme={null}
pub async fn set_allow_admin_reports(
    &self,
    jid: &Jid,
    allow: bool,
) -> Result<(), anyhow::Error>
```

**Parameters:**

* `jid` - Group JID
* `allow` - `true` to enable admin reports, `false` to disable

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;

// 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?;
```

### set\_group\_history

Enable or disable group history sharing.

```rust theme={null}
pub async fn set_group_history(&self, jid: &Jid, enabled: bool) -> Result<(), anyhow::Error>
```

**Parameters:**

* `jid` - Group JID
* `enabled` - `true` to enable group history, `false` to disable

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;

// Enable group history
client.groups().set_group_history(&group_jid, true).await?;

// Disable group history
client.groups().set_group_history(&group_jid, false).await?;
```

### set\_member\_link\_mode

Set who can share invite links. This uses the MEX (Mutation Exchange) protocol.

```rust theme={null}
pub async fn set_member_link_mode(
    &self,
    jid: &Jid,
    mode: MemberLinkMode,
) -> Result<(), MexError>
```

**Parameters:**

* `jid` - Group JID
* `mode` - `MemberLinkMode::AdminLink` to restrict to admins, `MemberLinkMode::AllMemberLink` to allow all members

**Example:**

```rust theme={null}
use whatsapp_rust::features::groups::MemberLinkMode;

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

// 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?;
```

<Note>
  This method uses the MEX protocol and returns `MexError` instead of `anyhow::Error`. See the [MEX API](/api/mex) for details on MEX errors.
</Note>

### set\_member\_share\_history\_mode

Set who can share message history with new members. This uses the MEX protocol.

```rust theme={null}
pub async fn set_member_share_history_mode(
    &self,
    jid: &Jid,
    mode: MemberShareHistoryMode,
) -> Result<(), MexError>
```

**Parameters:**

* `jid` - Group JID
* `mode` - `MemberShareHistoryMode::AdminShare` to restrict to admins, `MemberShareHistoryMode::AllMemberShare` to allow all members

**Example:**

```rust theme={null}
use whatsapp_rust::features::groups::MemberShareHistoryMode;

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

// Only admins can share history with new members
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?;
```

### set\_limit\_sharing

Enable or disable limit sharing in the group. This uses the MEX protocol.

```rust theme={null}
pub async fn set_limit_sharing(&self, jid: &Jid, enabled: bool) -> Result<(), MexError>
```

**Parameters:**

* `jid` - Group JID
* `enabled` - `true` to enable limit sharing, `false` to disable

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;

// Enable limit sharing
client.groups().set_limit_sharing(&group_jid, true).await?;

// Disable limit sharing
client.groups().set_limit_sharing(&group_jid, false).await?;
```

### cancel\_membership\_requests

Cancel pending membership requests from the requesting user's side.

```rust theme={null}
pub async fn cancel_membership_requests(
    &self,
    jid: &Jid,
    participants: &[Jid],
) -> Result<Vec<ParticipantChangeResponse>, anyhow::Error>
```

**Parameters:**

* `jid` - Group JID
* `participants` - Array of JIDs whose pending requests to cancel

**Returns:**

* `Vec<ParticipantChangeResponse>` - Result for each participant

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;
let to_cancel = vec!["15551234567@s.whatsapp.net".parse()?];

let results = client.groups().cancel_membership_requests(&group_jid, &to_cancel).await?;
```

### revoke\_request\_code

Revoke invitation codes from specific participants. This is an admin operation.

```rust theme={null}
pub async fn revoke_request_code(
    &self,
    jid: &Jid,
    participants: &[Jid],
) -> Result<Vec<ParticipantChangeResponse>, anyhow::Error>
```

**Parameters:**

* `jid` - Group JID
* `participants` - Array of participant JIDs whose invitation codes to revoke

**Returns:**

* `Vec<ParticipantChangeResponse>` - Result for each participant

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;
let to_revoke = vec!["15551234567@s.whatsapp.net".parse()?];

let results = client.groups().revoke_request_code(&group_jid, &to_revoke).await?;
```

### acknowledge

Acknowledge a group notification.

```rust theme={null}
pub async fn acknowledge(&self, jid: &Jid) -> Result<(), anyhow::Error>
```

**Parameters:**

* `jid` - Group JID

**Example:**

```rust theme={null}
let group_jid: Jid = "123456789@g.us".parse()?;
client.groups().acknowledge(&group_jid).await?;
```

### batch\_get\_info

Batch query group info for multiple groups at once.

```rust theme={null}
pub async fn batch_get_info(
    &self,
    jids: Vec<Jid>,
) -> Result<Vec<BatchGroupResult>, anyhow::Error>
```

**Parameters:**

* `jids` - List of group JIDs to query (max 10,000)

**Returns:**

* `Vec<BatchGroupResult>` - Result for each group

**Example:**

```rust theme={null}
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: {} (size: {:?})", id, size);
        }
        BatchGroupResult::Forbidden(id) => {
            println!("Forbidden: {}", id);
        }
        BatchGroupResult::NotFound(id) => {
            println!("Not found: {}", id);
        }
    }
}
```

### set\_profile\_picture

Set a group's profile picture. Admin operation.

```rust theme={null}
pub async fn set_profile_picture(
    &self,
    group_jid: &Jid,
    image_data: Vec<u8>,
) -> Result<SetProfilePictureResponse, anyhow::Error>
```

**Parameters:**

* `group_jid` - Group JID (must end with `@g.us`)
* `image_data` - JPEG image bytes. The caller is responsible for sizing/cropping the image (WhatsApp uses 640x640).

**Returns:**

* `SetProfilePictureResponse` - Contains the new picture ID

Passing empty `image_data` routes to removal, mirroring the own-picture API. Prefer [`remove_profile_picture`](#remove_profile_picture) when removal is the intent.

**Example:**

```rust theme={null}
use std::fs;

let group_jid: Jid = "120363012345678@g.us".parse()?;
let image_bytes = fs::read("group_avatar.jpg")?;

let response = client.groups().set_profile_picture(&group_jid, image_bytes).await?;
if let Some(id) = response.id {
    println!("Group picture updated: {}", id);
}
```

<Warning>
  The image must be a valid JPEG. Other formats are not supported. The caller must be a group admin.
</Warning>

### remove\_profile\_picture

Remove a group's profile picture. Admin operation.

```rust theme={null}
pub async fn remove_profile_picture(
    &self,
    group_jid: &Jid,
) -> Result<SetProfilePictureResponse, anyhow::Error>
```

**Parameters:**

* `group_jid` - Group JID

**Returns:**

* `SetProfilePictureResponse` - Confirmation of removal

**Example:**

```rust theme={null}
let group_jid: Jid = "120363012345678@g.us".parse()?;
client.groups().remove_profile_picture(&group_jid).await?;
```

See [`SetProfilePictureResponse`](/api/profile#setprofilepictureresponse) for the response shape.

### get\_profile\_pictures

Batch fetch group profile pictures.

```rust theme={null}
pub async fn get_profile_pictures(
    &self,
    group_jids: Vec<Jid>,
    picture_type: PictureType,
) -> Result<Vec<GroupProfilePicture>, anyhow::Error>
```

**Parameters:**

* `group_jids` - List of group JIDs (max 1,000)
* `picture_type` - `PictureType::Preview` for thumbnails or `PictureType::Image` for full-size

**Returns:**

* `Vec<GroupProfilePicture>` - Profile picture data for each group

**Example:**

```rust theme={null}
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);
    }
}
```

## Types

### GroupInfo

Cached group information returned by `query_info`. Contains participant list, addressing mode, and LID-to-phone mappings for privacy-addressed groups.

`query_info` returns `Arc<GroupInfo>` so that repeated lookups and warm sends share the same snapshot without deep-cloning the participants list.

```rust theme={null}
pub struct GroupInfo {
    pub participants: Vec<Jid>,
    pub addressing_mode: AddressingMode,
}
```

**Methods:**

| Method                                                                | Description                                    |
| --------------------------------------------------------------------- | ---------------------------------------------- |
| `phone_jid_for_lid_user(lid_user: &str) -> Option<&Jid>`              | Look up the phone JID for a LID user           |
| `lid_user_for_phone_user(phone_user: &str) -> Option<&CompactString>` | Look up the LID user string for a phone number |
| `phone_device_jid_to_lid(phone_device_jid: &Jid) -> Jid`              | Convert a phone-based device JID to LID format |

**Example:**

```rust theme={null}
let info = client.groups().query_info(&group_jid).await?; // Arc<GroupInfo>

// Access participants and addressing mode
for participant in &info.participants {
    println!("Participant: {}", participant);
}

// For LID-addressed groups, resolve phone numbers
if info.addressing_mode == AddressingMode::Lid {
    if let Some(phone_jid) = info.phone_jid_for_lid_user("lid_user_id") {
        println!("Phone: {}", phone_jid);
    }
}
```

### GroupSubject

Validated group name with 100 character limit.

```rust theme={null}
impl GroupSubject {
    pub fn new(subject: impl Into<String>) -> Result<Self, anyhow::Error>
    pub fn into_string(self) -> String
}
```

### GroupDescription

Validated group description with 2048 character limit.

```rust theme={null}
impl GroupDescription {
    pub fn new(description: impl Into<String>) -> Result<Self, anyhow::Error>
    pub fn into_string(self) -> String
}
```

### MemberAddMode

```rust theme={null}
pub enum MemberAddMode {
    AdminAdd,      // Only admins can add members
    AllMemberAdd,  // All members can add
}
```

### ParticipantType

Participant role within a group.

```rust theme={null}
pub enum ParticipantType {
    Member,     // Regular member
    Admin,      // Group admin
    SuperAdmin, // Group creator / super admin
}
```

**Methods:**

* `is_admin(&self) -> bool` - Returns `true` if admin or super admin

### MemberLinkMode

Controls who can use invite links to join the group.

```rust theme={null}
pub enum MemberLinkMode {
    AdminLink,      // Only admins can share invite links
    AllMemberLink,  // All members can share invite links
}
```

### MemberShareHistoryMode

Controls who can share message history with new members.

```rust theme={null}
pub enum MemberShareHistoryMode {
    AdminShare,      // Only admins can share history
    AllMemberShare,  // All members can share history
}
```

### MembershipApprovalMode

```rust theme={null}
pub enum MembershipApprovalMode {
    Off,  // No approval required
    On,   // Admin approval required
}
```

### GroupParticipantOptions

Options for specifying a participant when creating or modifying a group.

```rust theme={null}
pub struct GroupParticipantOptions {
    pub jid: Jid,
    pub phone_number: Option<Jid>,
    pub privacy: Option<Vec<u8>>,
}
```

**Constructors:**

* `GroupParticipantOptions::new(jid)` - Create from a JID
* `GroupParticipantOptions::from_phone(phone_number)` - Create from a phone number JID
* `GroupParticipantOptions::from_lid_and_phone(lid, phone_number)` - Create from a LID and phone number

**Builder methods:**

* `.with_phone_number(jid)` - Attach a phone number JID (for LID participants)
* `.with_privacy(token)` - Attach a privacy token (`tc_token` bytes)

<Note>
  You typically don't need to set the `privacy` field manually. When AB props are enabled, the library automatically resolves and attaches privacy tokens during `create_group` and `add_participants` operations.
</Note>

**Example:**

```rust theme={null}
use whatsapp_rust::features::groups::GroupParticipantOptions;

let participant = GroupParticipantOptions::new("15551234567@s.whatsapp.net".parse()?);
```

### JoinGroupResult

Result of joining a group via invite code.

```rust theme={null}
pub enum JoinGroupResult {
    Joined(Jid),          // Successfully joined
    PendingApproval(Jid), // Membership approval required
}
```

**Methods:**

* `group_jid(&self) -> &Jid` - Returns the group JID regardless of result variant

### MembershipRequest

A pending membership approval request.

```rust theme={null}
pub struct MembershipRequest {
    pub jid: Jid,
    pub request_time: Option<u64>,
}
```

### ParticipantChangeResponse

Result of a participant change operation (add, remove, approve, reject).

```rust theme={null}
#[non_exhaustive]
pub struct ParticipantChangeResponse {
    pub jid: Jid,
    pub status: Option<String>,
    pub error: Option<String>,
    pub add_request: Option<AddRequestInfo>,
}
```

<Note>
  `ParticipantChangeResponse` is `#[non_exhaustive]`. Field reads are unaffected; only exhaustive struct destructuring from outside the crate requires adding `..`.
</Note>

`add_request` is populated when the server responds with HTTP 403 carrying an `<add_request>` child — that happens when a participant has privacy blocked direct adds and the inviter must send them a v4 invite link out-of-band. Since v0.6 the value is preserved instead of being dropped, so consumers can drive the v4 invite flow without parsing the raw IQ.

```rust theme={null}
pub struct AddRequestInfo {
    pub code: String,        // v4 invite token
    pub expiration: i64,     // unix seconds; 0 means the server omitted it
}
```

### AddressingMode

```rust theme={null}
pub enum AddressingMode {
    Pn,   // Phone number addressing
    Lid,  // LID (privacy) addressing
}
```

### BatchGroupResult

Result for a single group in a batch query.

```rust theme={null}
pub enum BatchGroupResult {
    Full(Box<GroupMetadata>),           // Full group metadata
    Truncated { id: Jid, size: Option<u32> },  // Only ID and size returned
    Forbidden(Jid),                     // Access denied
    NotFound(Jid),                      // Group does not exist
}
```

### GrowthLockInfo

Growth lock information (system-managed, read-only). When present, invite links are temporarily disabled by the system.

```rust theme={null}
pub struct GrowthLockInfo {
    pub lock_type: String,
    pub expiration: u64,
}
```

### PictureType

Profile picture query type for batch fetching.

```rust theme={null}
pub enum PictureType {
    Preview,  // Thumbnail / preview size
    Image,    // Full-size image
}
```

### GroupProfilePicture

A single group profile picture result from a batch query.

```rust theme={null}
pub struct GroupProfilePicture {
    pub group_jid: Jid,
    pub url: Option<String>,
    pub direct_path: Option<String>,
    pub photo_id: Option<String>,
}
```

### CreateGroupResult

Result of creating a group.

```rust theme={null}
#[non_exhaustive]
pub struct CreateGroupResult {
    pub metadata: GroupMetadata,
}
```

<Note>
  `CreateGroupResult` is `#[non_exhaustive]`. Field reads are unaffected; only exhaustive struct destructuring from outside the crate requires adding `..`.
</Note>

The `metadata` field carries the full group state returned by the server (same shape as [`get_metadata`](#get_metadata)). `metadata.participants` is `Vec<GroupParticipant>` — note that masked-number `display_name` labels live on `GroupParticipantInfo` (which carries `<participant>` children of *notification* events), not on `GroupParticipant` here.

<Note>
  Prior to v0.6 this struct only exposed a `gid: Jid`. Replace `result.gid` with `result.metadata.id` when upgrading (`GroupMetadata.id`, not `jid`). The same migration applies to `CreateCommunityResult`.
</Note>

### GroupJoinError

Error codes returned when joining a group via invite fails.

```rust theme={null}
pub enum GroupJoinError {
    AlreadyMember,       // 304 - Already a member of the group
    BadRequest,          // 400 - Invalid request
    Forbidden,           // 403 - Not allowed to join
    NotFound,            // 404 - Group not found
    NotAllowed,          // 405 - Join method not allowed
    Conflict,            // 409 - Conflicting state
    Gone,                // 410 - Group no longer exists
    CommunityFull,       // 412 - Community has reached capacity
    GroupFull,           // 419 - Group has reached capacity
    Locked,              // 423 - Group is locked
    Unknown(u16),        // Other error code
}
```

**Methods:**

* `from_code(code: u16) -> Self` - Create from a numeric status code
* `code(&self) -> u16` - Get the numeric status code

### InviteInfoError

Error codes returned when fetching group info from an invite code.

```rust theme={null}
pub enum InviteInfoError {
    BadRequest,            // 400 - Invalid request
    NotAuthorized,         // 401 - Not authorized
    NotFound,              // 404 - Invite code not found
    NotAcceptable,         // 406 - Not acceptable
    Gone,                  // 410 - Invite code expired
    ParentGroupSuspended,  // 416 - Parent community is suspended
    Locked,                // 423 - Group is locked
    GrowthLocked,          // 436 - Group growth is temporarily locked
    Unknown(u16),          // Other error code
}
```

**Methods:**

* `from_code(code: u16) -> Self` - Create from a numeric status code
* `code(&self) -> u16` - Get the numeric status code

## Error handling

All methods return `Result<T, anyhow::Error>`. Common errors:

* Invalid JID format
* Network errors
* Permission denied (not admin)
* Group not found
* Subject/description too long
* Participant not found

```rust theme={null}
match client.groups().set_subject(&group_jid, subject).await {
    Ok(_) => println!("Subject updated"),
    Err(e) => eprintln!("Failed to update subject: {}", e),
}
```
