The ChatActions feature provides methods for managing chat organization through archiving, pinning, muting, starring messages, marking chats as read, deleting chats, and deleting individual messages. These operations sync across all your devices via WhatsApp’s app state sync mechanism.
Access
Access chat action operations through the client:
let chat_actions = client.chat_actions();
Archive
archive_chat
Archive a chat to hide it from the main chat list.
pub async fn archive_chat(
&self,
jid: &Jid,
message_range: Option<SyncActionMessageRange>,
) -> Result<(), AppStateError>
Parameters:
jid - The chat JID to archive
message_range - Optional message range for multi-device conflict resolution. Pass None in most cases
Example:
let jid: Jid = "15551234567@s.whatsapp.net".parse()?;
client.chat_actions().archive_chat(&jid, None).await?;
unarchive_chat
Unarchive a chat to show it in the main chat list.
pub async fn unarchive_chat(
&self,
jid: &Jid,
message_range: Option<SyncActionMessageRange>,
) -> Result<(), AppStateError>
Parameters:
jid - The chat JID to unarchive
message_range - Optional message range for multi-device conflict resolution. Pass None in most cases
Example:
client.chat_actions().unarchive_chat(&jid, None).await?;
Pin
pin_chat
Pin a chat to keep it at the top of the chat list.
pub async fn pin_chat(&self, jid: &Jid) -> Result<(), AppStateError>
Parameters:
jid - The chat JID to pin
Example:
let jid: Jid = "15551234567@s.whatsapp.net".parse()?;
client.chat_actions().pin_chat(&jid).await?;
WhatsApp limits the number of pinned chats. Attempting to pin too many chats may fail.
unpin_chat
Unpin a chat.
pub async fn unpin_chat(&self, jid: &Jid) -> Result<(), AppStateError>
Parameters:
jid - The chat JID to unpin
Example:
client.chat_actions().unpin_chat(&jid).await?;
Mute
mute_chat
Mute a chat indefinitely.
pub async fn mute_chat(&self, jid: &Jid) -> Result<(), AppStateError>
Parameters:
jid - The chat JID to mute
Example:
let jid: Jid = "15551234567@s.whatsapp.net".parse()?;
client.chat_actions().mute_chat(&jid).await?;
mute_chat_until
Mute a chat until a specific time.
pub async fn mute_chat_until(
&self,
jid: &Jid,
mute_end_timestamp_ms: i64
) -> Result<(), AppStateError>
Parameters:
jid - The chat JID to mute
mute_end_timestamp_ms - Unix timestamp in milliseconds when mute expires (must be in the future)
Example:
use chrono::{Utc, Duration};
let jid: Jid = "15551234567@s.whatsapp.net".parse()?;
// Mute for 8 hours
let mute_until = Utc::now() + Duration::hours(8);
client.chat_actions()
.mute_chat_until(&jid, mute_until.timestamp_millis())
.await?;
// Mute for 1 week
let mute_until = Utc::now() + Duration::weeks(1);
client.chat_actions()
.mute_chat_until(&jid, mute_until.timestamp_millis())
.await?;
unmute_chat
Unmute a chat.
pub async fn unmute_chat(&self, jid: &Jid) -> Result<(), AppStateError>
Parameters:
jid - The chat JID to unmute
Example:
client.chat_actions().unmute_chat(&jid).await?;
Star messages
star_message
Star a message to mark it as important.
pub async fn star_message(
&self,
chat_jid: &Jid,
participant_jid: Option<&Jid>,
message_id: &str,
from_me: bool
) -> Result<(), AppStateError>
Parameters:
chat_jid - The chat containing the message
participant_jid - For group messages from others, pass Some(&sender_jid). For 1-on-1 chats or your own messages, pass None
message_id - The message ID to star
from_me - Whether the message was sent by you
Example:
// Star your own message in a 1-on-1 chat
client.chat_actions()
.star_message(&chat_jid, None, "MESSAGE_ID", true)
.await?;
// Star someone else's message in a 1-on-1 chat
client.chat_actions()
.star_message(&chat_jid, None, "MESSAGE_ID", false)
.await?;
// Star someone else's message in a group
let sender_jid: Jid = "15559876543@s.whatsapp.net".parse()?;
client.chat_actions()
.star_message(&group_jid, Some(&sender_jid), "MESSAGE_ID", false)
.await?;
For group messages not sent by you, participant_jid is required. The method will return an error if it’s not provided.
unstar_message
Remove the star from a message.
pub async fn unstar_message(
&self,
chat_jid: &Jid,
participant_jid: Option<&Jid>,
message_id: &str,
from_me: bool
) -> Result<(), AppStateError>
Parameters:
Same as star_message.
Example:
client.chat_actions()
.unstar_message(&chat_jid, None, "MESSAGE_ID", true)
.await?;
Mark chat as read
mark_chat_as_read
Mark a chat as read or unread. This is distinct from mark_as_read (IQ receipts) — it syncs the read/unread state across all linked devices.
pub async fn mark_chat_as_read(
&self,
jid: &Jid,
read: bool,
message_range: Option<SyncActionMessageRange>,
) -> Result<(), AppStateError>
Parameters:
jid - The chat JID to mark
read - true to mark as read, false to mark as unread
message_range - Optional message range for multi-device conflict resolution. Pass None in most cases
Example:
let jid: Jid = "15551234567@s.whatsapp.net".parse()?;
// Mark chat as read
client.chat_actions().mark_chat_as_read(&jid, true, None).await?;
// Mark chat as unread
client.chat_actions().mark_chat_as_read(&jid, false, None).await?;
This syncs the read/unread badge across linked devices via app state sync (regular_low collection). To send read receipts to the sender, use client.mark_as_read() instead.
Delete chat
delete_chat
Delete a chat from the chat list across all linked devices.
pub async fn delete_chat(
&self,
jid: &Jid,
delete_media: bool,
message_range: Option<SyncActionMessageRange>,
) -> Result<(), AppStateError>
Parameters:
jid - The chat JID to delete
delete_media - Whether to also delete downloaded media files
message_range - Optional message range for multi-device conflict resolution. Pass None in most cases
Example:
let jid: Jid = "15551234567@s.whatsapp.net".parse()?;
// Delete chat and its media
client.chat_actions().delete_chat(&jid, true, None).await?;
// Delete chat but keep media files
client.chat_actions().delete_chat(&jid, false, None).await?;
This operation is not reversible. The chat and optionally its media will be removed from all linked devices.
Clear chat
clear_chat
Clear a chat’s messages while keeping the chat itself (WhatsApp Web’s “Clear chat”). Unlike delete_chat, the chat stays in the list — only its messages are removed. Syncs across all linked devices.
pub async fn clear_chat(
&self,
jid: &Jid,
delete_starred: bool,
delete_media: bool,
message_range: Option<SyncActionMessageRange>,
) -> Result<(), AppStateError>
Parameters:
jid - The chat JID to clear
delete_starred - Also remove starred messages
delete_media - Also remove downloaded media files
message_range - Optional message range for multi-device conflict resolution. Pass None in most cases
Both flags are encoded in the mutation index (not the proto body), matching WhatsApp Web’s clearChat action.
Example:
let jid: Jid = "15551234567@s.whatsapp.net".parse()?;
// Clear all messages, including starred ones and downloaded media
client.chat_actions().clear_chat(&jid, true, true, None).await?;
// Clear messages but keep starred messages and media
client.chat_actions().clear_chat(&jid, false, false, None).await?;
A clear performed on another linked device arrives as an Event::ClearChatUpdate.
Save or rename a contact, syncing the name to your other linked devices (WhatsApp Web’s contact-sync action).
pub async fn save_contact(
&self,
jid: &Jid,
full_name: Option<String>,
first_name: Option<String>,
save_on_primary_addressbook: bool,
) -> Result<(), AppStateError>
Parameters:
jid - The contact’s JID. Must be a bare phone-number JID — LIDs and device-specific JIDs are rejected (LID contacts use a separate path on WhatsApp Web).
full_name - Full display name, or None
first_name - Short name, or None (omitted when absent; WhatsApp Web derives no default)
save_on_primary_addressbook - Whether to save the name to the phone’s address book
Example:
let jid: Jid = "15551234567@s.whatsapp.net".parse()?;
client.chat_actions()
.save_contact(&jid, Some("Jane Doe".into()), Some("Jane".into()), true)
.await?;
Status mute
set_user_status_mute
Mute or unmute a contact, group, or channel’s status updates across linked devices (WhatsApp Web’s userStatusMute). This is distinct from mute_chat, which silences a chat’s message notifications.
pub async fn set_user_status_mute(
&self,
jid: &Jid,
muted: bool,
) -> Result<(), AppStateError>
Parameters:
jid - The entity whose status updates to mute/unmute
muted - true hides their status updates, false unmutes
Example:
let jid: Jid = "15551234567@s.whatsapp.net".parse()?;
client.chat_actions().set_user_status_mute(&jid, true).await?; // mute status
client.chat_actions().set_user_status_mute(&jid, false).await?; // unmute
A status-mute change on another linked device arrives as an Event::UserStatusMuteUpdate.
Delete message for me
delete_message_for_me
Delete a specific message locally (not for the other party). This is different from revoke_message which deletes for everyone.
pub async fn delete_message_for_me(
&self,
chat_jid: &Jid,
participant_jid: Option<&Jid>,
message_id: &str,
from_me: bool,
delete_media: bool,
message_timestamp: Option<i64>,
) -> Result<(), AppStateError>
Parameters:
chat_jid - The chat containing the message
participant_jid - For group messages from others, pass Some(&sender_jid). For 1-on-1 chats or your own messages, pass None
message_id - The ID of the message to delete
from_me - Whether the message was sent by you
delete_media - Whether to also delete the associated media file
message_timestamp - Optional timestamp of the message
Example:
// Delete your own message in a 1-on-1 chat
client.chat_actions()
.delete_message_for_me(&chat_jid, None, "MESSAGE_ID", true, true, None)
.await?;
// Delete someone else's message in a group
let sender_jid: Jid = "15559876543@s.whatsapp.net".parse()?;
client.chat_actions()
.delete_message_for_me(&group_jid, Some(&sender_jid), "MESSAGE_ID", false, true, None)
.await?;
For group messages not sent by you, participant_jid is required. The method will return an error if it’s not provided.
This only removes the message from your own devices. The other party can still see the message. To delete for everyone, use client.revoke_message() instead.
Helper functions
message_range
Construct a SyncActionMessageRange for multi-device conflict resolution. In most cases you can pass None instead — only WhatsApp Web with a full message database populates this.
pub fn message_range(
last_message_timestamp: i64,
last_system_message_timestamp: Option<i64>,
messages: Vec<(wa::MessageKey, i64)>,
) -> SyncActionMessageRange
message_key
Construct a MessageKey for use with message_range.
pub fn message_key(
id: impl Into<String>,
remote_jid: &Jid,
from_me: bool,
participant: Option<&Jid>,
) -> wa::MessageKey
Generic app state action
send_app_state_action
Send any syncd (app state) Set action, driven by a generated schema from the whatsapp_rust::schemas registry. Use this as the escape hatch when there isn’t a dedicated helper yet (for example, clear_chat, favorites, quick_reply). The typed methods on ChatActions and Labels are thin wrappers over this same call.
pub async fn send_app_state_action(
&self,
schema: &Schema,
index_args: &[&str],
value: &wa::SyncActionValue,
) -> Result<(), AppStateError>
Parameters:
schema — A &Schema constant from whatsapp_rust::schemas (re-exported from wacore::appstate::schemas). The schema decides the collection, action version, and index shape.
index_args — The non-literal index parts in the order declared by schema.index_parts. Literal slots (the action name prefix) are filled automatically.
value — A wa::SyncActionValue with the matching action sub-field set and a timestamp in epoch milliseconds.
When to use it:
- The action you need does not have a typed helper on
ChatActions or Labels.
- You need to interoperate with a schema added to the registry without waiting for a new helper to land.
Prefer the typed wrappers (pin_chat, mute_chat, archive_chat, label methods, etc.) whenever they exist — they handle the timestamp, conflict-resolution fields, and index args for you.
Example:
use whatsapp_rust::schemas;
use whatsapp_rust::waproto::whatsapp as wa;
let value = wa::SyncActionValue {
clear_chat_action: Some(Default::default()),
timestamp: Some(1_700_000_000_000),
..Default::default()
};
// CLEAR_CHAT's non-literal index parts are [chatJid, deleteStarred, deleteMedia].
client
.send_app_state_action(
&schemas::CLEAR_CHAT,
&["15551234567@s.whatsapp.net", "0", "0"],
&value,
)
.await?;
Index arguments are positional and must match schema.index_parts length and order, excluding IndexPart::Literal slots. Mismatched arity returns an error before any patch is sent.
App state sync
All chat actions are synced across devices using WhatsApp’s app state synchronization:
| Action | Collection |
|---|
| Archive | regular_low |
| Pin | regular_low |
| Mark chat as read | regular_low |
| Mute | regular_high |
| Star | regular_high |
| Delete chat | regular_high |
| Delete message for me | regular_high |
| Clear chat | regular_high |
| Status mute | regular_high |
| Save contact | critical_unblock_low |
App state sync requires encryption keys to be available. These are typically obtained during initial sync after authentication. Actions may fail if called immediately after pairing before sync completes.
Events
Chat action changes are emitted as events that you can handle:
use wacore::types::events::Event;
.on_event(|event, _client| async move {
match &*event {
Event::MuteUpdate(update) => {
println!("Chat {} muted: {:?}", update.jid, update.action.muted);
}
Event::PinUpdate(update) => {
println!("Chat {} pinned: {:?}", update.jid, update.action.pinned);
}
Event::ArchiveUpdate(update) => {
println!("Chat {} archived: {:?}", update.jid, update.action.archived);
}
Event::StarUpdate(update) => {
println!("Message {} starred: {:?}", update.message_id, update.action.starred);
}
Event::MarkChatAsReadUpdate(update) => {
println!("Chat {} marked as read: {:?}", update.jid, update.action.read);
}
Event::DeleteChatUpdate(update) => {
println!("Chat {} deleted (media: {})", update.jid, update.delete_media);
}
Event::ClearChatUpdate(update) => {
println!("Chat {} cleared (media: {})", update.jid, update.delete_media);
}
Event::UserStatusMuteUpdate(update) => {
println!("Status of {} muted: {}", update.jid, update.muted);
}
Event::DeleteMessageForMeUpdate(update) => {
println!("Message {} in {} deleted for me", update.message_id, update.chat_jid);
}
_ => {}
}
})
Error handling
All methods return Result<(), AppStateError>:
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum AppStateError {
#[error("invalid app-state request: {0}")]
InvalidRequest(String),
#[error(transparent)]
Internal(#[from] anyhow::Error),
}
You’ll encounter these most often:
InvalidRequest — you passed an invalid timestamp to mute_chat_until or omitted participant_jid for a group operation
Internal — no app state sync key is available yet (sync not complete), or a network error occurred
use whatsapp_rust::AppStateError;
match client.chat_actions().mute_chat_until(&jid, 0).await {
Ok(_) => println!("Muted"),
Err(AppStateError::InvalidRequest(msg)) => eprintln!("Validation error: {}", msg),
Err(e) => eprintln!("Failed: {}", e),
}
Complete example
use whatsapp_rust::Client;
use wacore_binary::jid::Jid;
use chrono::{Utc, Duration};
use std::sync::Arc;
async fn organize_chats(client: &Arc<Client>) -> anyhow::Result<()> {
let important_chat: Jid = "15551234567@s.whatsapp.net".parse()?;
let noisy_group: Jid = "123456789@g.us".parse()?;
let old_chat: Jid = "15559876543@s.whatsapp.net".parse()?;
// Pin important conversations
client.chat_actions().pin_chat(&important_chat).await?;
// Mute noisy group for 1 week
let mute_until = Utc::now() + Duration::weeks(1);
client.chat_actions()
.mute_chat_until(&noisy_group, mute_until.timestamp_millis())
.await?;
// Archive old conversations
client.chat_actions().archive_chat(&old_chat, None).await?;
// Star an important message
client.chat_actions()
.star_message(&important_chat, None, "IMPORTANT_MSG_ID", false)
.await?;
// Mark chat as read across all devices
client.chat_actions()
.mark_chat_as_read(&important_chat, true, None)
.await?;
// Delete a message locally (not for everyone)
client.chat_actions()
.delete_message_for_me(&old_chat, None, "OLD_MSG_ID", false, true, None)
.await?;
// Delete an old chat and its media
client.chat_actions().delete_chat(&old_chat, true, None).await?;
Ok(())
}
See also
- Events - Handle chat action update events
- Groups - Group management operations
- Client - Core client API