> ## Documentation Index
> Fetch the complete documentation index at: https://whatsapp-rust.jlucaso.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Community management

> Learn how to create communities, manage subgroups, query community metadata, send encrypted CAG reactions, and post channel comments in whatsapp-rust

## Overview

Communities are parent groups that contain linked subgroups. They use the `w:g2` IQ namespace for mutations and MEX (GraphQL) for metadata queries.

This guide covers creating communities, linking and unlinking subgroups, querying subgroup metadata, identifying group types, sending encrypted reactions to Community Announcement Groups, and posting channel comments.

## Accessing the Community API

All community operations are accessed through the `community()` method:

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

See [Community API reference](/api/community) for the full API.

## Creating a community

```rust theme={null}
use whatsapp_rust::features::community::CreateCommunityOptions;

let options = CreateCommunityOptions::new("My Community");

let result = client.community().create(options).await?;
println!("Community created: {}", result.gid);
```

By default, `create_general_chat` is `true`, so a general chat subgroup is created alongside the community.

See [Community API reference](/api/community#create) for details.

### Community creation options

Customize the community with `CreateCommunityOptions`:

```rust theme={null}
use whatsapp_rust::features::community::CreateCommunityOptions;

let mut options = CreateCommunityOptions::new("My Community");
options.description = Some("Welcome to the community!".to_string());
options.closed = true;
options.allow_non_admin_sub_group_creation = false;
options.create_general_chat = true;

let result = client.community().create(options).await?;
```

<Note>
  If a description is provided, it is set via a follow-up IQ after creation — the group create stanza does not support inline descriptions for communities.
</Note>

## Deactivating a community

Deactivate (delete) a community. Subgroups are unlinked but not deleted:

```rust theme={null}
client.community().deactivate(&community_jid).await?;
```

See [Community API reference](/api/community#deactivate) for details.

## Managing subgroups

### Link existing groups

Link existing groups as subgroups of a community:

```rust theme={null}
let subgroup_jids = vec![
    "120363001111111@g.us".parse()?,
    "120363002222222@g.us".parse()?,
];

let result = client.community()
    .link_subgroups(&community_jid, &subgroup_jids)
    .await?;

println!("Linked: {:?}", result.linked_jids);

for (jid, error_code) in &result.failed_groups {
    eprintln!("Failed to link {}: error code {}", jid, error_code);
}
```

See [Community API reference](/api/community#link_subgroups) for details.

### Create a new subgroup

Create a brand new group that's already linked as a subgroup of a community, in a single call:

```rust theme={null}
let participants = vec![
    "15551234567@s.whatsapp.net".parse()?,
];

let result = client.community()
    .create_subgroup("Announcements", &participants, &community_jid)
    .await?;

println!("Created subgroup: {} ({})", result.metadata.subject, result.metadata.id);
```

This is a shortcut for creating a group and then calling `link_subgroups` — both steps happen in a single round-trip instead of two.

See [Community API reference](/api/community#create_subgroup) for details.

### Unlink subgroups

Unlink subgroups from a community:

```rust theme={null}
let result = client.community()
    .unlink_subgroups(
        &community_jid,
        &subgroup_jids,
        true, // remove_orphan_members
    )
    .await?;

println!("Unlinked: {:?}", result.unlinked_jids);
```

When `remove_orphan_members` is `true`, members who are only in the community through the unlinked subgroups are removed from the community.

See [Community API reference](/api/community#unlink_subgroups) for details.

### Remove participants from a community

Remove participants directly from a community (as opposed to from a single subgroup):

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

let results = client.community()
    .remove_participants(&community_jid, &to_remove)
    .await?;

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

See [Community API reference](/api/community#remove_participants) for details.

### Join a subgroup

Join a linked subgroup via the parent community:

```rust theme={null}
let metadata = client.community()
    .join_subgroup(&community_jid, &subgroup_jid)
    .await?;

println!("Joined: {} ({} participants)",
    metadata.subject, metadata.participants.len());
```

See [Community API reference](/api/community#join_subgroup) for details.

## Querying community information

### List communities you're in

Fetch all parent/community groups the logged-in account currently participates in:

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

for (jid, metadata) in communities {
    println!("{}: {} ({} participants)", jid, metadata.subject, metadata.participants.len());
}
```

See [Community API reference](/api/community#get_participating) for details.

### List subgroups

Fetch all subgroups of a community via MEX (GraphQL):

```rust theme={null}
let subgroups = client.community()
    .get_subgroups(&community_jid)
    .await?;

for sg in &subgroups {
    println!("{}: {} (default: {}, general: {})",
        sg.id, sg.subject, sg.is_default_sub_group, sg.is_general_chat);
    if let Some(count) = sg.participant_count {
        println!("  Participants: {}", count);
    }
}
```

See [Community API reference](/api/community#get_subgroups) for details.

### Get subgroup participant counts

Fetch participant counts per subgroup without fetching full subgroup details:

```rust theme={null}
let counts = client.community()
    .get_subgroup_participant_counts(&community_jid)
    .await?;

for (jid, count) in &counts {
    println!("{}: {} participants", jid, count);
}
```

See [Community API reference](/api/community#get_subgroup_participant_counts) for details.

### Query linked group metadata

Query a specific linked subgroup's metadata from the parent community:

```rust theme={null}
let metadata = client.community()
    .query_linked_group(&community_jid, &subgroup_jid)
    .await?;

println!("Subject: {}", metadata.subject);
println!("Participants: {}", metadata.participants.len());
```

See [Community API reference](/api/community#query_linked_group) for details.

### Get all participants across subgroups

Fetch all participants across all linked groups of a community:

```rust theme={null}
let participants = client.community()
    .get_linked_groups_participants(&community_jid)
    .await?;

for p in &participants {
    println!("{} (admin: {})", p.jid, p.is_admin);
}
```

See [Community API reference](/api/community#get_linked_groups_participants) for details.

## Identifying group types

Use the `group_type` function to classify a group within the community hierarchy:

```rust theme={null}
use whatsapp_rust::features::community::{group_type, GroupType};

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

match group_type(&metadata) {
    GroupType::Community => println!("This is a community parent group"),
    GroupType::LinkedSubgroup => println!("This is a linked subgroup"),
    GroupType::LinkedAnnouncementGroup => println!("This is the default announcement subgroup"),
    GroupType::LinkedGeneralGroup => println!("This is the general chat subgroup"),
    GroupType::Default => println!("This is a regular group"),
    _ => println!("Unknown group type"),
}
```

The classification is based on these `GroupMetadata` fields:

| Field                                | Description                                    |
| ------------------------------------ | ---------------------------------------------- |
| `is_parent_group`                    | `true` for community parent groups             |
| `parent_group_jid`                   | JID of the parent community (for subgroups)    |
| `is_default_sub_group`               | `true` for the default announcement subgroup   |
| `is_general_chat`                    | `true` for the general chat subgroup           |
| `allow_non_admin_sub_group_creation` | Whether non-admin members can create subgroups |

See [Groups API reference](/api/groups#get_metadata) for all `GroupMetadata` fields.

## Community Announcement Group (CAG) reactions

The default announcement subgroup of a community — the one where `is_default_sub_group` is `true` — is a Community Announcement Group (CAG). CAGs require encrypted reactions; plaintext reactions are silently dropped by the server.

`client.send_reaction()` handles this transparently. The same call works for DMs, regular groups, and CAGs with no change to your code:

```rust theme={null}
let target_key = wa::MessageKey {
    remote_jid: Some(cag_jid.to_string()),
    from_me: Some(false),
    id: Some("3EB0POSTID".to_string()),
    // participant must be the post author.
    participant: Some(post_author_jid.to_string()),
};

// send_reaction takes target_key by value; clone it to reuse for removal.
client.send_reaction(&cag_jid, target_key.clone(), "🔥").await?;

// Remove a reaction with an empty emoji:
client.send_reaction(&cag_jid, target_key, "").await?;
```

For CAG chats the library checks `GroupInfo::is_community_announce` (populated from metadata and cached). When `true`, the reaction is encrypted with the target post's `messageSecret` (captured when the post was received) and shipped as an `enc_reaction_message` envelope. If the parent secret is not available the call fails with a descriptive error rather than emitting a plaintext reaction the channel would drop.

Incoming encrypted reactions from CAG posts are decrypted transparently by the receive path and surfaced as a normal `reaction_message` event. The `key` field is filled from the envelope's `target_message_key`, so your event handler looks identical to a regular group reaction:

```rust theme={null}
Event::Messages(batch) => {
    for InboundMessage { message: msg, .. } in batch.iter() {
        if let Some(reaction) = msg.reaction_message.as_option() {
            println!("Reaction: {:?}", reaction.text);
            println!("On post:  {:?}", reaction.key.as_option().and_then(|k| k.id.as_deref()));
        }
    }
}
```

## Channel comments

Post encrypted threaded replies under a CAG post using `client.comments()`:

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

let parent_key = wa::MessageKey {
    remote_jid: Some(cag_jid.to_string()),
    from_me: Some(false),
    id: Some("3EB0POSTID".to_string()),
    // participant must point to the post author so receivers can derive the
    // decryption key from the envelope. The library resolves the author
    // automatically when from_me is true and participant is omitted.
    participant: Some(post_author_jid.to_string()),
};

// Send a text comment:
let result = client.comments()
    .send_text(&cag_jid, parent_key.clone(), "Great post!")
    .await?;
println!("Comment sent: {}", result.message_id);
```

For arbitrary message bodies, use `send_message`:

```rust theme={null}
let body = wa::Message {
    extended_text_message: buffa::MessageField::some(wa::message::ExtendedTextMessage {
        text: Some("Great post!".to_string()),
        ..Default::default()
    }),
    ..Default::default()
};

let result = client.comments()
    .send_message(&cag_jid, parent_key, body)
    .await?;
```

Comments require the parent post's `messageSecret` to have been captured when the post was received (via `msg_secret_policy`). If it was not captured the call returns an error explaining that the secret is missing.

Each comment carries a fresh `messageSecret` of its own so it can receive encrypted reactions. The comment's secret is persisted under the comment's own id and sender.

### Receiving comments

Incoming encrypted comments are decrypted transparently on the receive path. The decrypted body is dispatched as part of a normal `Event::Messages` batch. Because the inner `Message` proto has no slot for the parent post key, the threading link surfaces on `MessageInfo::comment_target`:

```rust theme={null}
Event::Messages(batch) => {
    for InboundMessage { message: msg, info, .. } in batch.iter() {
        if let Some(parent_key) = &info.comment_target {
            // This is a channel comment.
            println!("Comment on post: {:?}", parent_key.id);

            if let Some(text) = msg.text_content() {
                println!("Comment text: {}", text);
            }
        }
    }
}
```

`comment_target` is `None` for all other message types.

## Error handling

Community mutations return `anyhow::Error`, while MEX-based queries (`get_subgroups`, `get_subgroup_participant_counts`) return `MexError`:

```rust theme={null}
use whatsapp_rust::features::mex::MexError;

match client.community().get_subgroups(&community_jid).await {
    Ok(subgroups) => println!("Found {} subgroups", subgroups.len()),
    Err(MexError::PayloadParsing(msg)) => {
        eprintln!("Parse error: {}", msg);
    }
    Err(e) => eprintln!("Error: {}", e),
}
```

## Next steps

* [Community API reference](/api/community) — Full API details and types
* [Group management](/guides/group-management) — Manage individual groups
* [Sending messages](/guides/sending-messages#reactions) — Reactions API overview
* [Receiving messages](/guides/receiving-messages) — Handle message events including comments
* [Events](/concepts/events) — Handle group update events
* [MEX API](/api/mex) — Understand the GraphQL layer used by community queries
