Project Overview
DisGo is an idiomatic Go library for building Discord bots and applications. It provides a high-level interface over Discord’s REST and Gateway APIs, plus integrated modules for Webhooks, OAuth2, Voice, caching, and more.
Key Features
- Full REST API client with builders for messages, embeds, reactions, threads, etc.
- Gateway support with configurable intents, presence, and sharding
- Slash commands, components, and modal interactions via built-in HTTP server
- OAuth2 flows for bot invitations and user authorization
- Voice connections and audio playback
- Pluggable cache backend or disable caching entirely
- Structured event system with both handler interfaces and listener functions
- Built-in logging (slog) and configurable via
bot.WithLogger
When to Use DisGo
- You need a type-safe, production-ready Go client for Discord
- You want fine-grained control over Gateway intents and REST rate limits
- You prefer a modular architecture with easy extension points
- You value built-in support for interactions (slash commands, buttons, modals)
Project Status
- Active development with semantic versioning (see
SemVersion
constant) - Requires Go 1.24+
- Unit-tested core modules; community contributions welcome
Supported Discord Features
- Gateway Intents (guilds, messages, reactions, voice state, etc.)
- Slash commands, context menus, message components, modals
- Thread creation and management
- Webhooks (create, edit, delete, execute)
- OAuth2 authorization code and client credentials grants
- Voice connection lifecycle and audio frames
Licensing
DisGo is licensed under the Apache License 2.0. See the full text in the LICENSE
file.
You may use, modify, and distribute DisGo under the terms of Apache 2.0, subject to the conditions outlined in the license.
Getting Help
- Documentation: https://pkg.go.dev/github.com/disgoorg/disgo
- Examples: https://github.com/disgoorg/examples
- GitHub Issues: https://github.com/disgoorg/disgo/issues
- Community Chat: Join our Discord server (link in README) for real-time support and discussion
For detailed guides and advanced patterns, refer to the examples repository and the GoDoc pages.
Getting Started
This guide walks you through installing DisGo, creating a bot token, writing and running a minimal bot, and verifying your setup with build and test commands.
Prerequisites
- Go 1.24 or newer
- A Discord application with a Bot user and its token
GUILD_MESSAGES
intent enabled in the Discord Developer Portal
1. Install DisGo
Initialize your module and add DisGo:
go mod init github.com/yourusername/yourbot
go get github.com/disgoorg/disgo
2. Create and Export Your Bot Token
- Open https://discord.com/developers/applications
- Select your application → Bot → Copy Token
- Export it in your shell:
export BOT_TOKEN="YOUR_BOT_TOKEN_HERE"
3. Write a Minimal Bot
Create main.go
:
package main
import (
"context"
"log"
"os"
"os/signal"
"syscall"
"github.com/disgoorg/disgo"
"github.com/disgoorg/disgo/bot"
"github.com/disgoorg/disgo/events"
"github.com/disgoorg/disgo/gateway"
)
func main() {
// 1. Instantiate client with token and message intent
client, err := disgo.New(
os.Getenv("BOT_TOKEN"),
bot.WithGatewayConfigOpts(
gateway.WithIntents(gateway.IntentGuildMessages),
),
// 2. Register a simple MessageCreate listener
bot.WithEventListenerFunc(func(e *events.MessageCreate) {
if e.Message.Content == "ping" {
_, _ = e.Client().Rest().CreateMessage(e.ChannelID, "pong")
}
}),
)
if err != nil {
log.Fatalf("failed to create client: %v", err)
}
// 3. Open gateway connection
if err := client.OpenGateway(context.Background()); err != nil {
log.Fatalf("failed to open gateway: %v", err)
}
log.Println("Bot is now running")
// 4. Wait for interrupt signal to shut down gracefully
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
<-stop
client.CloseGateway(context.Background())
log.Println("Bot stopped")
}
4. Build and Run
go build -o mybot
./mybot
Your bot logs “Bot is now running” and responds with “pong” whenever it sees “ping” in a guild channel.
5. Verify with Tests
Run DisGo’s test suite and your own code:
go test ./...
6. (Optional) Automate with GitHub Actions
Add .github/workflows/go.yml
:
name: Go CI
on:
push:
paths:
- '**/*.go'
- '**/go.mod'
- '.github/workflows/go.yml'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: {go-version: '1.24'}
- run: go build -v ./...
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: {go-version: '1.24'}
- run: go test -v ./...
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: {go-version: '1.24'}
- uses: golangci/golangci-lint-action@v6
with: {version: latest}
This workflow builds, tests, and lints on each push to keep your bot code healthy.
Core Concepts
Disgo’s architecture centers on a high-level bot.Client
that wires together configurable components—gateway, HTTP server, REST, voice, cache, sharding and event handling. Understanding each piece and how they integrate helps you build robust bots.
1. bot.Client
Overview*bot.Client
provides:
- Gateway/shard lifecycle (
OpenGateway
,Close
) - HTTP interaction server (
OpenHTTPServer
) - REST API calls (
Rest()
) - Cache access (
Cache()
) - Event management (
AddEventListeners
,EventManager()
) - Shard control (
ShardManager()
, if enabled)
Key Methods
client, _ := disgo.New(token,
bot.WithDefaultGateway(),
bot.WithDefaultShardManager(),
bot.WithEventListenerFunc(func(e *events.Ready) {
fmt.Println("Ready as", e.User.Username)
}),
)
ctx := context.Background()
client.OpenGateway(ctx) // starts gateway shards
client.OpenHTTPServer() // starts slash-command server
defer client.Close(ctx) // clean shutdown
Accessing Subsystems
// Send a message via REST
client.Rest().CreateMessage(channelID, bot.NewCreateMessage(
bot.MessageCreateContent("Hello"),
))
// Read from cache
member, ok := client.Cache().Members().Get(guildID, userID)
// Find which shard handles a guild
shard, _ := client.ShardManager().ShardForGuild(guildID)
2. Configuration with ConfigOpt
Disgo uses functional options (ConfigOpt
) to swap or tune components before building the client.
Common bot
options
WithDefaultGateway()
,WithGatewayConfigOpts(...)
WithDefaultShardManager()
,WithShardManagerConfigOpts(...)
WithRestClientConfigOpts(...)
,WithCacheConfigOpts(...)
WithEventListeners(...)
,WithEventListenerFunc(...)
WithHTTPServerConfigOpts(publicKey, ...)
Example: custom REST timeout and cache flags
client, _ := disgo.New(token,
bot.WithRestClientConfigOpts(
rest.WithTimeout(10*time.Second),
),
bot.WithCacheConfigOpts(
cache.WithCaches(cache.FlagGuilds|cache.FlagMembers),
),
)
3. Event Management
EventManager
routes gateway and HTTP events to your listeners.
Registering listeners
client.AddEventListeners(
&events.MessageCreateListener{
HandleMessageCreate: func(e *events.MessageCreate) {
log.Println("New message:", e.Message.Content)
},
},
)
// or
client.AddEventListenerFunc(func(e *events.GuildMemberAdd) {
fmt.Println("Member joined:", e.Member.User.Username)
})
Dispatch behavior
- Listeners run synchronously by default
- Use
bot.WithEventDispatcher(async bool)
to toggle async dispatching
4. Cache
cache.NewCache[T any]
provides a thread-safe store keyed by snowflake IDs. Disgo pre-builds caches for guilds, members, channels, etc.
Basic usage
// Create a new cache for custom entities
myCache := cache.NewCache[MyType](https://github.com/DisgoOrg/disgo/blob/master/cache.Config{MaxSize: 1000})
// Store and retrieve
myCache.Put(id, entity)
entity, ok := myCache.Get(id)
// Remove or iterate
myCache.Remove(id)
for id, ent := range myCache.All() {
// process ent
}
Integrate into bot.Client
client, _ := disgo.New(token,
bot.WithCacheConfigOpts(
cache.WithCustomCache(cache.FlagChannels, myCache),
),
)
5. Gateway
gateway.Gateway
manages a single WebSocket connection.
Creating and opening
gw := gateway.New(
token,
func(eventType gateway.EventType, sequence int, shardID int, data gateway.EventData) {
// raw event handling
},
func(gw gateway.Gateway, err error) {
log.Println("Gateway closed:", err)
},
gateway.WithIntents(gateway.IntentGuildMessages, gateway.IntentDirectMessages),
gateway.WithAutoReconnect(true),
)
ctx := context.Background()
gw.Open(ctx)
defer gw.Close()
Key features
- Heartbeats and resume logic
- Configurable intents, compression, rate-limiting
- Raw event hooks via
WithEnableRawEvents(true)
6. Sharding
sharding.ShardManager
controls multiple gateway shards.
Initialization
sm := shardmanager.New(
token,
shardmanager.WithTotalShards(4),
shardmanager.WithIntents(gateway.IntentGuilds|gateway.IntentGuildMessages),
)
ctx := context.Background()
sm.Open(ctx) // opens all shards
defer sm.Close()
Runtime controls
// Get shard by guild
shard, _ := sm.ShardForGuild(guildID)
// Re-shard on the fly
sm.Reshard(10) // scale to 10 shards
// Listen for shard events
sm.AddListenerFunc(func(event shardmanager.ShardEvent) {
log.Println("Shard", event.ShardID(), "status:", event.Status())
})
Understanding these core building blocks lets you customize connections, handle events efficiently, maintain local state, and scale across Discord’s sharding requirements.
Usage Guides
Practical how-to guides for common tasks with Disgo’s core modules.
Slash Command & Component Routing (handler)
Define declarative routes for slash commands, buttons, modals, and apply middleware.
package main
import (
"context"
"log"
"github.com/disgoorg/disgo"
"github.com/disgoorg/disgo/events"
"github.com/disgoorg/disgo/handler"
"github.com/disgoorg/disgo/handler/middleware"
"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/snowflake/v2"
)
func main() {
token := "Bot YOUR_BOT_TOKEN"
client, err := disgo.New(token)
if err != nil {
log.Fatalf("error creating client: %v", err)
}
// Build router
r := handler.New()
r.Use(middleware.Logger()) // log all events
r.NotFound(func(evt *handler.CommandEvent) error {
return evt.CreateMessage(discord.MessageCreate{Content: "Command not found"})
})
// Top-level /ping command
r.Command("/ping", func(evt *handler.CommandEvent) error {
return evt.CreateMessage(discord.MessageCreate{Content: "Pong!"})
})
// Grouped commands
r.Group(func(r handler.Router) {
r.Use(middleware.Print("admin"))
// /admin ban <user>
r.Command("/admin/ban", func(evt *handler.CommandEvent) error {
user := evt.Data.(discord.SlashCommandInteractionData).OptUser("user")
return evt.CreateMessage(discord.MessageCreate{Content: "Banned " + user.User().Username})
})
})
// Register in a guild (replace with your guild ID)
guildID := snowflake.ID(123456789012345678)
if err := handler.SyncCommands(client.Rest(), r.Commands(), []snowflake.ID{guildID}); err != nil {
log.Fatalf("sync commands: %v", err)
}
client.OpenGatewayIntent(context.Background(), disgo.GatewayIntents{
disgo.IntentGuilds | disgo.IntentGuildMessages | disgo.IntentGuildMessageReactions,
})
select {}
}
- Build a
handler.Router
, attach middleware viaUse
. - Define
Command
andComponent
routes with paths. - Call
handler.SyncCommands
before opening the gateway. - Supply a
NotFound
fallback for unmatched interactions.
OAuth2 Client & Session Management
Perform Discord OAuth2 flows: generate URLs, obtain tokens, fetch user data.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"github.com/disgoorg/disgo/oauth2"
"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/snowflake/v2"
)
var (
sessions = map[string]*oauth2.Session{}
oauth *oauth2.Client
)
func main() {
clientID := snowflake.GetEnv("CLIENT_ID")
clientSecret := os.Getenv("CLIENT_SECRET")
oauth = oauth2.New(clientID, clientSecret)
http.HandleFunc("/login", handleLogin)
http.HandleFunc("/callback", handleCallback)
http.HandleFunc("/", handleRoot)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func handleLogin(w http.ResponseWriter, r *http.Request) {
url := oauth.GenerateAuthorizationURL(oauth2.AuthorizationURLParams{
RedirectURI: "http://localhost:8080/callback",
Scopes: []discord.OAuth2Scope{discord.OAuth2ScopeIdentify, discord.OAuth2ScopeEmail},
})
http.Redirect(w, r, url, http.StatusFound)
}
func handleCallback(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
sess, _, err := oauth.StartSession(code, state)
if err != nil {
http.Error(w, "OAuth2 error: "+err.Error(), http.StatusInternalServerError)
return
}
sessions[state] = sess
http.SetCookie(w, &http.Cookie{Name: "session", Value: state, Path: "/"})
http.Redirect(w, r, "/", http.StatusFound)
}
func handleRoot(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie("session")
if err != nil || sessions[cookie.Value] == nil {
http.Redirect(w, r, "/login", http.StatusFound)
return
}
sess := sessions[cookie.Value]
user, err := oauth.GetUser(sess)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data, _ := json.MarshalIndent(user, "", " ")
fmt.Fprintf(w, "<pre>%s</pre>", data)
}
- Initialize with
oauth2.New(clientID, clientSecret)
. - Redirect users via
GenerateAuthorizationURL
. - Exchange
code
for tokens withStartSession
. - Store
Session
(in-memory or in DB). - Call
GetUser
,GetConnections
, etc., using the session.
REST Client: Unified API Access
Instantiate a REST client and call any Discord HTTP endpoint.
package main
import (
"context"
"log"
"github.com/disgoorg/disgo"
"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/disgo/rest"
"github.com/disgoorg/snowflake/v2"
)
func main() {
token := "Bot YOUR_BOT_TOKEN"
cli := rest.New(rest.NewClient(token))
// Fetch channel info
channelID := snowflake.ID(987654321098765432)
channel, err := cli.GetChannel(context.Background(), channelID)
if err != nil {
log.Fatalf("GetChannel: %v", err)
}
log.Printf("Channel: %s (type=%d)", channel.Name, channel.Type)
// Send a message
msg, err := cli.CreateMessage(context.Background(), channelID, discord.NewMessageCreateBuilder().
SetContent("Hello from REST client!").
Build(),
)
if err != nil {
log.Fatalf("CreateMessage: %v", err)
}
log.Printf("Sent message ID: %s", msg.ID)
}
- Use
rest.New(rest.NewClient(token))
for a full-featured REST interface. - Pass a
context.Context
to control cancellations and timeouts. - Build requests with builders (e.g.
NewMessageCreateBuilder
).
Voice Connection Management (voice.Manager)
Manage voice connections per guild, handle updates, create and close connections.
package main
import (
"context"
"log"
"time"
"github.com/disgoorg/disgo"
"github.com/disgoorg/disgo/events"
"github.com/disgoorg/disgo/voice"
"github.com/disgoorg/snowflake/v2"
)
func main() {
token := "Bot YOUR_BOT_TOKEN"
client, _ := disgo.New(token, disgo.WithEventListenerFunc(onVoiceState), disgo.WithEventListenerFunc(onVoiceServer))
client.OpenGatewayIntent(context.Background(), disgo.GatewayIntents{
disgo.IntentGuildVoiceStates,
})
select {}
}
func onVoiceState(evt *events.VoiceStateUpdate) {
client := evt.Client().VoiceManager
client.HandleVoiceStateUpdate(evt)
}
func onVoiceServer(evt *events.VoiceServerUpdate) {
client := evt.Client().VoiceManager
client.HandleVoiceServerUpdate(evt)
guildID := evt.GuildID
channelID := snowflake.ID(123456789012345678)
// Create or get existing connection
conn, err := client.CreateConn(guildID)
if err != nil {
log.Println("CreateConn:", err)
return
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := conn.Open(ctx, channelID, false, false); err != nil {
log.Println("Open voice:", err)
return
}
defer conn.Close(ctx)
// Mark speaking and send a silence frame
conn.SetSpeaking(ctx, voice.SpeakingFlagMicrophone)
conn.UDP().Write(voice.SilenceAudioFrame)
// ... stream or echo audio here ...
}
- Listen for
VoiceStateUpdate
andVoiceServerUpdate
events. - Call
VoiceManager.HandleVoice…
on each event. - Use
CreateConn(guildID)
to obtain a connection, thenOpen
andClose
. - Use
SetSpeaking
,UDP().Write(...)
to send Opus packets.
Webhook Client: Send & Manage Messages
Create a webhook client from ID/token, then send, update, or delete messages.
package main
import (
"context"
"log"
"github.com/disgoorg/disgo"
"github.com/disgoorg/disgo/discord"
"github.com/disgoorg/disgo/rest"
"github.com/disgoorg/disgo/webhook"
"github.com/disgoorg/snowflake/v2"
)
func main() {
id := snowflake.GetEnv("WEBHOOK_ID")
token := "YOUR_WEBHOOK_TOKEN"
client := webhook.New(id, token)
defer client.Close(context.Background())
// Send a message
msg, err := client.CreateMessage(discord.NewWebhookMessageCreateBuilder().
SetContent("Hello via webhook").
Build(),
rest.CreateWebhookMessageParams{Wait: true},
)
if err != nil {
log.Fatal("CreateMessage:", err)
}
log.Println("Sent webhook message ID:", msg.ID)
// Update the message
updated, err := client.UpdateMessage(msg.ID, discord.NewWebhookMessageUpdateBuilder().
SetContent("Edited content").
Build(),
)
if err != nil {
log.Fatal("UpdateMessage:", err)
}
log.Println("Updated content:", updated.Content)
// Delete the message
if err := client.DeleteMessage(updated.ID); err != nil {
log.Fatal("DeleteMessage:", err)
}
}
- Use
webhook.New(id, token)
to instantiate. - Always
defer client.Close(ctx)
to clean up. - Pass
rest.CreateWebhookMessageParams{Wait: true}
to await Discord’s response. - Builders simplify payload creation (
NewWebhookMessageCreateBuilder
,NewWebhookMessageUpdateBuilder
).
Advanced Topics
Deep-dive configurations and extensions for production deployments and power users.
Proxy Setup for Gateway & REST Traffic
Define your proxies and disable local rate limiting/compression so external proxies handle those concerns.
Environment Variables
DISGO_TOKEN=YOUR_DISCORD_BOT_TOKEN
DISGO_GUILD_ID=YOUR_GUILD_ID
DISGO_GATEWAY_URL=ws://gateway-proxy:7878
DISGO_REST_URL=http://rest-proxy:7979/api/v10
Initializing the Disgo Client
package main
import (
"context"
"log"
"os"
"time"
"github.com/DisgoOrg/disgo/bot"
"github.com/DisgoOrg/disgo/sharding"
"github.com/DisgoOrg/disgo/rest"
"github.com/DisgoOrg/disgo/gateway"
"github.com/DisgoOrg/disgo/events"
"github.com/DisgoOrg/disgo/discord"
)
func main() {
token := os.Getenv("DISGO_TOKEN")
guildID := discord.Snowflake(os.Getenv("DISGO_GUILD_ID"))
gatewayURL := os.Getenv("DISGO_GATEWAY_URL")
restURL := os.Getenv("DISGO_REST_URL")
client, err := disgo.New(token,
// Shard manager opts: custom gateway + no compression/rate limiting
bot.WithShardManagerConfigOpts(
sharding.WithGatewayConfigOpts(
gateway.WithURL(gatewayURL),
gateway.WithCompress(false),
),
sharding.WithRateLimiter(sharding.NewNoopRateLimiter()),
),
// REST client opts: custom REST endpoint, no local rate limiting
bot.WithRestClientConfigOpts(
rest.WithURL(restURL),
rest.WithRateLimiter(rest.NewNoopRateLimiter()),
),
bot.WithEventListenerFunc(commandListener),
)
if err != nil {
log.Fatalf("failed to build Disgo client: %v", err)
}
defer client.Close(context.Background())
// Register slash commands at startup
commands := []discord.ApplicationCommandCreate{
discord.SlashCommandCreate{
Name: "say",
Description: "Echoes your message",
Options: []discord.ApplicationCommandOption{
discord.ApplicationCommandOptionString{
Name: "message",
Description: "Text to echo",
Required: true,
},
discord.ApplicationCommandOptionBool{
Name: "ephemeral",
Description: "Ephemeral response?",
Required: true,
},
},
},
}
if _, err := client.Rest.SetGuildCommands(client.ApplicationID, guildID, commands); err != nil {
log.Fatalf("failed to register commands: %v", err)
}
// Block until shutdown signal
select {}
}
func commandListener(event *events.ApplicationCommandInteractionCreate) {
data := event.SlashCommandInteractionData()
if data.CommandName() != "say" {
return
}
msg := discord.NewMessageCreateBuilder().
SetContent(data.String("message")).
SetEphemeral(data.Bool("ephemeral")).
Build()
if _, err := event.CreateMessage(msg); err != nil {
log.Printf("failed to send response: %v", err)
}
}
Gateway Rate Limiter
Control outgoing gateway commands to stay within Discord’s rate limits.
Creating a Limiter
import (
"context"
"log"
"time"
"github.com/DisgoOrg/disgo/gateway"
)
limiter := gateway.NewRateLimiter(
gateway.WithCommandsPerMinute(150),
gateway.WithLogger(myLogger),
)
Sending Payloads
Wrap each send with Wait
/Unlock
:
ctx := context.Background()
for _, payload := range payloads {
if err := limiter.Wait(ctx); err != nil {
log.Fatalf("rate limiter wait failed: %v", err)
}
if err := wsConn.WriteJSON(payload); err != nil {
limiter.Unlock() // prevent deadlock
log.Printf("send error: %v", err)
continue
}
limiter.Unlock()
}
Resetting and Shutdown
// On reconnect
limiter.Reset()
// On application shutdown
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := limiter.Close(ctx); err != nil {
log.Printf("limiter close timeout: %v", err)
}
Internals & Tips
- Wait sleeps when commands are exhausted until the 1-minute window resets.
- Unlock decrements the counter and resets the window on first use after expiry.
- Always pair
Wait
andUnlock
, even on errors. - Use
Reset
when you reestablish the gateway connection.
REST Rate Limiter
Manage HTTP request quotas per-route and globally, unlocking based on Discord’s rate‐limit headers.
Creating a REST Rate Limiter
import (
"log"
"time"
"github.com/DisgoOrg/disgo/rest"
)
restLimiter := rest.NewRateLimiter(
rest.WithBucketExpiration(15*time.Minute),
rest.WithCleanupInterval(5*time.Minute),
rest.WithLogger(myLogger),
)
Injecting into the Client
client, err := disgo.New(token,
bot.WithRestClientConfigOpts(
rest.WithRateLimiter(restLimiter),
),
)
How It Works
- Each request acquires a per-route bucket lock before sending.
- After the response, it reads
X-RateLimit-Remaining
,X-RateLimit-Reset
and unlocks the bucket with the new reset time. - Expired buckets are cleaned up periodically to free memory.
Common Usage Pattern
// Example: fetching a channel
ch, err := client.Rest.GetChannel(channelID)
if err != nil {
log.Printf("failed to fetch channel: %v", err)
}
All REST calls automatically respect locks and unlocks under the hood.
Practical Tips
- Tune
WithBucketExpiration
to control memory use for inactive routes. - Use
WithCleanupInterval
to balance CPU vs. memory. - Provide a logger to diagnose rate‐limit hits and resets.
Examples Catalog
An index of runnable Disgo samples grouped by theme. Each entry links to a focused example with setup, core logic, and practical tips.
Listening to Gateway Events
Register and handle Discord gateway events via:- Functional listeners
- Channel-based listeners
- Listener adapters
Quickly pick the pattern that fits your bot’s architecture.
Voice Audio Echo
Connect a bot to a voice channel, set speaking state, and echo incoming Opus packets.
Ideal for testing voice latency or learning raw UDP audio handling.Auto Moderation Example
Programmatically create, update, and delete Auto Moderation rules.
Subscribe to rule lifecycle and execution events for auditing or notifications.OAuth2 Client: User Authentication and Session Management
Integrategithub.com/disgoorg/disgo/oauth2.Client
into an HTTP server.
Guide covers redirecting users, exchanging codes, persisting sessions via cookies, and fetching user/connection data.
Contributing & Development
This section explains how to report bugs, request features, run CI checks locally, adhere to code style, and submit contributions to DisgoOrg/disgo.
Reporting Bugs
Use our bug report template to provide complete information and speed up triage.
- Navigate to Issues > New Issue > Bug Report or open
.github/ISSUE_TEMPLATE/bug_report.md
- Fill in all sections:
- Describe the bug: concise summary
- Error: copy-paste logs or stack traces
- To Reproduce: minimal Go example
- Expected behavior: what you expected
- Version: Disgo version, Go version, OS
- Additional context: screenshots or links
Example snippet:
**Describe the bug**
Creating an embed with `SetTitle("")` panics.
**Error**
panic: runtime error: invalid memory address
**To Reproduce**
```go
package main
import "github.com/DisgoOrg/disgo/components"
func main() {
_ = components.NewEmbed().SetTitle("").Build()
}
Expected behavior Empty title yields an error or is ignored.
### Requesting Features
Use our feature request template to propose enhancements.
1. Go to **Issues > New Issue > Feature Request** or open `.github/ISSUE_TEMPLATE/feature_request.md`
2. Provide:
- **Problem**: what you’re facing
- **Solution**: your proposed API or behavior
- **Alternatives**: approaches you considered
- **Additional context**: links to docs, related issues
### Running CI Checks Locally
We mirror GitHub Actions (`.github/workflows/go.yml`) in your workstation:
1. Install Go 1.24
2. From repo root, run:
```bash
# Format and organize imports
go fmt ./…
goimports -w .
# Run tests with coverage
go test ./… -coverprofile=coverage.out
# Lint using golangci-lint (requires .golangci.yml)
golangci-lint run --timeout 5m
- Verify no formatting diffs:
git diff --exit-code
Code Style & Formatting
We enforce consistent style via .editorconfig
:
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 4
max_line_length = 180
[*.go]
indent_style = tab
ij_go_run_go_fmt_on_reformat = true
ij_go_import_sorting = gofmt
ij_go_GROUP_CURRENT_PROJECT_IMPORTS = true
[{*.md,*.markdown}]
indent_style = tab
max_line_length = 600
[{*.yaml,*.yml}]
indent_style = space
indent_size = 2
Practical tips:
- Install EditorConfig plugin in your IDE (VSCode, IntelliJ, Vim).
- Rely on
gofmt
/goimports
integration for.go
files. - CI still runs
go fmt ./…
andgolangci-lint run
.
Automated Labeling
We auto-label PRs via .github/labeler.yml
:
"type:bugfix":
- head-branch: ['^fix/', '^bugfix/', '^hotfix/']
"type:docs":
- head-branch: ['^docs/', '^documentation/']
"t:gateway":
- changed-files:
- any-glob-to-any-file: ['gateway/**']
Rules:
- head-branch regex matches your branch name.
- changed-files globs match paths you modify.
To add a label:
- Edit
.github/labeler.yml
, add a new key matching an existing GitHub label. - Define
head-branch
patterns and/orchanged-files
globs. - Commit and open a PR—new rules apply on next PR.
Submitting Contributions
- Fork
DisgoOrg/disgo
and clone your fork. - Create a feature branch:
git checkout -b feat/my-new-feature
- Implement your changes, run local CI checks (see above).
- Commit with a clear message:
feat(commands): add support for slash subcommands
- Push to your fork and open a PR against
main
:- Use the Pull Request template if provided.
- Reference related issues:
Closes #123
.
- Ensure all CI checks pass before requesting review.
By following these guidelines, you help maintain code quality and streamline collaboration. We appreciate your contributions!