discord.rs is a standalone Discord bot framework for Rust with typed models, typed gateway events, Components V2, collectors, cache managers, and an HTTP client.
Brand name: discord.rs. The crates.io package name and Rust import path remain discordrs.
- Typed
Clientruntime withEventenum dispatch and compatibilityBotClientalias - Typed
RestClientwith shared route/global rate-limit state and compatibilityDiscordHttpClientalias prelude::*re-exports for common runtime, builder, helper, and response types- Cache-backed manager reads for guilds, channels, members, roles, presences, and messages, with bounded defaults, cheap
Arcread APIs for hot member/message/presence paths, and explicitCacheConfigoverrides - Collectors for messages, interactions, components, and modals behind the
collectorsfeature - Gateway WebSocket client with connect, heartbeat, identify, resume, reconnect, terminal close-code handling, and fixed compressed binary frame decoding for explicit
zlib-streamconnections - Shard supervisor and shard messenger control paths for queued shard boot, reconnect, shutdown, presence, and voice state updates
- Voice manager plus voice runtime support for websocket hello/identify, UDP discovery, select-protocol, speaking updates, raw UDP receive, AES-GCM/XChaCha RTP-size Opus packet decrypt, pure-Rust Opus PCM decode, and Opus-frame RTP send helpers
- Live-validated
davefeature with DAVE opcode state tracking,davey/OpenMLS-backed MLS lifecycle helpers, receive decryptor hooks, and outbound media encryption hooks - Optional OAuth2 backend helpers for authorization URLs, authorization-code exchange, and refresh-token exchange
- Typed Discord coverage for all official REST route shapes audited on 2026-05-02, plus Webhook Events, lobbies, guild incident actions, audit logs, guild count fetches, guild modifications, guild channel creation and reordering, guild ban pagination, single-member ban bodies, guild member profile fields and search/list pagination, current-user guild pagination/counts, guild/member/current-member edits, guild role create/update/reordering bodies, guild widget/welcome/onboarding writes, guild prune count/result including the current JSON-body begin route, guild-member join, role member-count, public widget, Stage Instance writes, sticker pack fetches, typed guild sticker writes, voice-state REST reads/writes, current-application and OAuth2 metadata reads, Create Group DM and Group DM recipient routes, channel invite/target-user and permission routes, voice-channel status updates, guild message search, current and legacy channel-pin routes, forwarded message snapshots, shared client themes, Gateway rate-limit, reaction metadata, and presence metadata events, Activity instances, polls, subscriptions, entitlements, soundboard, threads, forum channel fields, invites, integrations, Auto Moderation, guild preview/vanity, voice regions, OAuth2 user connections, application command permissions, and bulk bans
- Application framework routing for slash commands, components, and modals behind the
interactionsfeature - Components V2 builders (
Container,TextDisplay,Section,MediaGallery,Button,SelectMenuwith auto-populated defaults, and more) - Typed command builders for slash, user, and message commands
- Modal builders with
RadioGroup,CheckboxGroup,Checkbox, andFileUpload - V2 modal submission parser with preserved
FileUpload,RadioGroup,CheckboxGroup, and other V2 component types - Typed interaction decoding with chat-input, context-menu, autocomplete, component, and modal submit variants
- Interaction routing helpers:
parse_interaction,parse_raw_interaction,parse_interaction_context, andtry_interactions_endpoint - Feature-gated runtime layers:
gateway,interactions,collectors,sharding, andvoice; the in-memorycachestorage is enabled by default and can still be disabled withdefault-features = false
[dependencies]
discordrs = "2.0.2"[dependencies]
# Gateway bot client
discordrs = { version = "2.0.2", features = ["gateway"] }
# HTTP Interactions Endpoint
discordrs = { version = "2.0.2", features = ["interactions"] }
# Gateway runtime with default cache storage
discordrs = { version = "2.0.2", features = ["gateway"] }
# Minimal core without cache storage
discordrs = { version = "2.0.2", default-features = false }
# Gateway runtime with collectors
discordrs = { version = "2.0.2", features = ["gateway", "collectors"] }
# Sharding foundations
discordrs = { version = "2.0.2", features = ["gateway", "sharding"] }
# Voice foundations
discordrs = { version = "2.0.2", features = ["voice"] }
# PCM -> Opus voice encode/playback helpers
discordrs = { version = "2.0.2", features = ["voice", "voice-encode"] }
# DAVE receive/outbound media integration
discordrs = { version = "2.0.2", features = ["voice", "dave"] }
# Gateway runtime with zstd-stream transport compression
discordrs = { version = "2.0.2", features = ["gateway", "zstd-stream"] }
# Both runtime modes
discordrs = { version = "2.0.2", features = ["gateway", "interactions"] }Recent API cleanup tightened the public surface:
RestClientnow exposes the typed REST methods as the supported public path. The old raw convenience methods such assend_message,edit_message,create_dm_channel,create_interaction_response, andbulk_overwrite_global_commandsare no longer public.- Builder implementation submodules are private. Import builders from
discordrs::builders::{...}or use the crate root re-exports. ApplicationCommandno longer implementsDiscordModelbecause its ID is optional until Discord assigns one. UseApplicationCommand::id_opt()andApplicationCommand::created_at()instead.
If you are upgrading existing code, the common replacements are:
| Old path | New path |
|---|---|
RestClient::send_message(...) |
send_message(...) helper or RestClient::create_message(...) |
RestClient::edit_message(...) |
RestClient::update_message(...) |
RestClient::create_dm_channel(...) |
RestClient::create_dm_channel_typed(...) |
RestClient::create_interaction_response(...) |
RestClient::create_interaction_response_typed(...) or typed helper functions |
RestClient::bulk_overwrite_global_commands(...) |
RestClient::bulk_overwrite_global_commands_typed(...) |
discordrs::builders::modal::* |
discordrs::builders::{...} or crate root re-exports |
generic DiscordModel access for ApplicationCommand |
ApplicationCommand::id_opt() / ApplicationCommand::created_at() |
A navigation-focused docs website (similar to the discord.js docs browsing style) is available in discordrsdocs/:
- Live URL:
http://discordrs.teamwicked.me/discordrsdocs/#/ - Korean URL:
http://discordrs.teamwicked.me/discordrsdocs/#/ko/README - Entry:
discordrsdocs/index.html - Testing guide:
discordrsdocs/docs/guide/testing-and-coverage.md - Local preview:
python3 -m http.server 8080 --directory discordrsdocs
use async_trait::async_trait;
use discordrs::{gateway_intents, Client, Context, Event, EventHandler};
struct Handler;
#[async_trait]
impl EventHandler for Handler {
async fn handle_event(&self, _ctx: Context, event: Event) {
match event {
Event::Ready(ready) => {
println!("Bot ready! User: {}", ready.data.user.username);
}
Event::MessageCreate(message) => {
println!("Message: {}", message.message.content);
}
_ => {}
}
}
}
#[tokio::main]
async fn main() {
let token = std::env::var("DISCORD_TOKEN").unwrap();
Client::builder(&token, gateway_intents::GUILDS | gateway_intents::GUILD_MESSAGES)
.event_handler(Handler)
.start()
.await
.unwrap();
}use discordrs::{
option_type, CommandOptionBuilder, PermissionsBitField, SlashCommandBuilder,
};
let command = SlashCommandBuilder::new("ticket", "Create a support ticket")
.option(
CommandOptionBuilder::new(option_type::STRING, "topic", "Ticket topic")
.required(true)
.autocomplete(true),
)
.default_member_permissions(PermissionsBitField(0))
.build();use discordrs::{
button_style, create_container, send_container_message, ButtonConfig, DiscordHttpClient,
};
async fn send_support_panel(http: &DiscordHttpClient, channel_id: u64) -> Result<(), discordrs::DiscordError> {
let buttons = vec![
ButtonConfig::new("ticket_open", "Open Ticket").style(button_style::PRIMARY),
ButtonConfig::new("ticket_status", "Check Status").style(button_style::SECONDARY),
];
let container = create_container(
"Support Panel",
"Use the buttons below to manage support requests.",
buttons,
None,
);
send_container_message(http, channel_id, container).await?;
Ok(())
}use discordrs::{
CheckboxBuilder, CheckboxGroupBuilder, FileUploadBuilder, ModalBuilder, RadioGroupBuilder,
SelectOption,
};
let modal = ModalBuilder::new("preferences_modal", "Preferences")
.add_radio_group(
"Theme",
Some("Pick one"),
RadioGroupBuilder::new("theme")
.add_option(SelectOption::new("Light", "light"))
.add_option(SelectOption::new("Dark", "dark"))
.required(true),
)
.add_checkbox_group(
"Notifications",
Some("Choose any"),
CheckboxGroupBuilder::new("notify_channels")
.add_option(SelectOption::new("Email", "email"))
.add_option(SelectOption::new("Push", "push"))
.min_values(0)
.max_values(2),
)
.add_checkbox(
"Agree to Terms",
None,
CheckboxBuilder::new("agree_terms").required(true),
)
.add_file_upload(
"Screenshot",
Some("Attach one or more files"),
FileUploadBuilder::new("attachments").min_values(1),
);use async_trait::async_trait;
use axum::Router;
use discordrs::{
create_container, InteractionContext, InteractionHandler, InteractionResponse, RawInteraction,
try_interactions_endpoint,
};
#[derive(Clone)]
struct Handler;
#[async_trait]
impl InteractionHandler for Handler {
async fn handle(
&self,
ctx: InteractionContext,
interaction: RawInteraction,
) -> InteractionResponse {
match interaction {
RawInteraction::Command { name, .. } if name.as_deref() == Some("hello") => {
let data = serde_json::json!({
"components": [create_container("Hello", "World", vec![], None).build()],
"flags": 1 << 15,
});
let _ = ctx;
InteractionResponse::ChannelMessage(data)
}
_ => InteractionResponse::Raw(serde_json::json!({ "type": 5 })),
}
}
}
fn app(public_key: &str) -> Router {
try_interactions_endpoint(public_key, Handler)
.expect("invalid Discord public key")
}| Feature | Description | Key deps |
|---|---|---|
| (default) | Builders, typed models, command builders, parsers, REST client, helpers, and in-memory cache storage | reqwest, serde_json, tokio, async-trait |
gateway |
Gateway WebSocket, Client, typed Event, and EventHandler::handle_event(...) dispatch |
tokio-tungstenite, flate2, async-trait |
zstd-stream |
Gateway zstd-stream transport compression | gateway, zstd |
interactions |
HTTP Interactions Endpoint with Ed25519 | axum, ed25519-dalek |
cache |
Enables the in-memory cache storage and CacheBackend extension trait used by gateway cache managers; included in default features |
tokio, async-trait |
collectors |
Async collectors for messages and interactions | tokio |
sharding |
Sharding manager and reusable gateway config abstractions | tokio |
voice |
Voice connection/player skeletons plus voice gateway/UDP receive, Opus-frame send, transport decrypt, and Opus PCM decode helpers | tokio, aes-gcm, chacha20poly1305, opus-decoder |
voice-encode |
PCM source/mixer and opus-rs encoder helpers for 48 kHz stereo 20 ms voice playback through the existing Opus frame path |
voice, opus-rs |
dave |
DAVE/MLS receive and outbound media hooks backed by davey, with live Discord MLS transition validation coverage |
voice, davey |
discord.rsstarted as a helper around serenity workflows, and1.0.0is the first stabilized standalone framework release with the typed runtime surface.- Use
try_interactions_endpoint()when you want invalid public keys to fail at startup instead of during requests. - Use
discordrs::prelude::*when you want the shortest path to the main runtime, command, helper, and response APIs. - Use
DiscordHttpClient::create_followup_message_with_application_id()when you already haveInteractionContext.application_idand the client was not initialized with an application id. - Prefer the typed
RestClientmethods such ascreate_message,update_message,create_interaction_response_typed, andbulk_overwrite_*_typed. - Prefer typed REST wrappers such as
bulk_guild_ban,get_guild_role,get_guild_audit_log_typed,get_auto_moderation_rules_typed,get_guild_preview_typed,get_guild_vanity_url,get_voice_regions_typed,get_guild_voice_regions,get_current_application, andget_application_role_connection_metadata_recordsbefore falling back to rawserde_json::Valuemethods. CacheHandle::new()uses bounded cache defaults so normal gateway builds do not retain every gateway entity forever.- Use
Client::builder(...).cache_config(...)orCacheHandle::with_config(...)to tune cache limits, and useCacheConfig::unbounded()only when unbounded retention is intentional. - Use
CacheHandle::is_enabled()when code may be compiled withdefault-features = false; cache storage is enabled in the default feature set. - Use
member_arc(...),message_arc(...),presence_arc(...), and the managercached_arc(...)helpers when hot cache reads should avoid deep cloning large cached payloads. - Implement
CacheBackendwhen experimenting with an external member/message/presence store;CacheHandleimplements the same trait for the default in-memory backend. - Invite codes, webhook tokens, interaction tokens, and other token-like REST path segments are validated before authenticated paths are built.
- Generated REST query strings are percent-encoded, and repeated HTTP 429 responses are retried up to a bounded limit before returning
DiscordError::RateLimit. - Use
OAuth2Clientfor OAuth2 authorization-code or refresh-token flows; it is intentionally separate from bot-tokenRestClientauthorization. - Token-authenticated
/interactions/...and/webhooks/...requests intentionally omit botAuthorizationheaders, and token/path segments are validated before webhook/callback paths are built. - Typed slash and autocomplete interaction payloads preserve option
value,focused, and nested option input throughCommandInteractionOption. - Use
Clientfor new gateway code.BotClientremains available as a compatibility alias. - Use
EventHandler::handle_event(...)when you want one typed entry point for every gateway event. Legacy convenience callbacks such asready,message_create, andinteraction_createstill exist, but they now receive typed payloads too. - Use
Context::new(...)when tests or helper crates need a standalone context outside the live gateway runtime. - Use
examples/live_dave_capture_bot.rsto capture matching botVOICE_STATE_UPDATEandVOICE_SERVER_UPDATEvalues before running the ignored live DAVE validation test. - Prefer builder imports from
discordrs::builders::{...}or the crate root re-exports. Deeper implementation submodules are private. - Use
ApplicationCommand::id_opt()until Discord has assigned an ID. Unsaved commands are no longer treated as genericDiscordModels. - The parser keeps V2 modal component types, including
FileUpload,RadioGroup, andCheckboxGroup, so routing logic can keep full fidelity.
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
- ingwannu
- Contact: ingwannu@teamwicked.me, ingwannu@gmail.com