A modern Swift library for building Discord bots and integrations.
Built with async/await, strongly typed, and cross-platform.
SwiftDisc is a powerful Swift library for interacting with the Discord API. It embraces modern Swift concurrency with async/await throughout, provides fully typed models for Discord's data structures, and handles common pain points like rate limiting, reconnection, and sharding automatically.
Whether you're building a simple bot or a complex integration, SwiftDisc gives you the tools you need while staying out of your way.
Add SwiftDisc to your Swift package dependencies in Package.swift:
dependencies: [
.package(url: "https://github.com/M1tsumi/SwiftDisc.git", from: "1.1.0")
]Then add it to your target:
targets: [
.target(name: "YourBot", dependencies: ["SwiftDisc"])
]| Platform | Minimum Version |
|---|---|
| macOS | 11.0+ |
| iOS | 14.0+ |
| tvOS | 14.0+ |
| watchOS | 7.0+ |
| Windows | Swift 5.9+ |
Here's a simple bot that responds to messages:
import SwiftDisc
@main
struct MyBot {
static func main() async {
let token = ProcessInfo.processInfo.environment["DISCORD_BOT_TOKEN"] ?? ""
let client = DiscordClient(token: token)
do {
try await client.loginAndConnect(intents: [.guilds, .guildMessages, .messageContent])
for await event in client.events {
switch event {
case .ready(let info):
print("✅ Logged in as \(info.user.username)")
case .messageCreate(let message) where message.content == "!ping":
try await client.sendMessage(
channelId: message.channel_id,
content: "🏓 Pong!"
)
default:
break
}
}
} catch {
print("❌ Error: \(error)")
}
}
}- Gateway Connection: WebSocket with automatic heartbeat, session resume, and event streaming
- REST API: Comprehensive coverage of Discord's HTTP endpoints
- Rate Limiting: Automatic per-route and global rate limit handling
- Sharding: Built-in support for large bots with health monitoring
- Type Safety: Strongly typed models throughout with compile-time safety
- Cross-Platform: Works on macOS, iOS, tvOS, watchOS, and Windows
- Command Framework: Built-in router for prefix and slash commands
- Component Builders: Fluent API for buttons, select menus, embeds, and modals
- View Manager: Persistent UI views with automatic lifecycle management
- Collectors: AsyncStream-based message and component collectors
- Extensions/Cogs: Modular architecture for organizing bot features
- Utilities: Mention formatters, emoji helpers, timestamp formatting, and more
The REST API covers all essential Discord features:
✅ Messages, embeds, reactions, threads
✅ Channels, permissions, webhooks
✅ Guilds, members, roles, bans
✅ Slash commands, autocomplete, modals
✅ Components (buttons, select menus)
✅ Scheduled events, stage instances
✅ Auto-moderation rules
✅ Application commands and interactions
For a complete API checklist, see the REST API Coverage section below.
let router = CommandRouter(prefix: "!")
router.register("ping") { ctx in
try? await ctx.reply("Pong!")
}
client.onMessageCreate { message in
await router.processMessage(message)
}Create command-based bots easily with the built-in router:
let router = CommandRouter(prefix: "!")
router.register("ping") { ctx in
try? await ctx.reply("Pong!")
}
client.onMessageCreate { message in
await router.processMessage(message)
}Add checks and cooldowns to commands:
router.register("ban", checks: [isAdminCheck], cooldown: 10.0) { ctx in
// Command logic here
}let slash = SlashCommandRouter()
slash.register("greet") { interaction in
try await interaction.reply("Hello from SwiftDisc!")
}Use the fluent builders to create rich messages:
let embed = EmbedBuilder()
.title("Welcome!")
.description("Thanks for joining our server")
.color(0x5865F2)
.timestamp(Date())
.build()
let button = ButtonBuilder()
.style(.primary)
.label("Click me!")
.customId("welcome_button")
.build()
let row = ActionRowBuilder()
.addButton(button)
.build()
try await client.sendMessage(
channelId: channelId,
embeds: [embed],
components: [row]
)Collect messages or component interactions using AsyncStreams:
let collector = client.createMessageCollector(
filter: { $0.author.id == userId && $0.channel_id == channelId },
timeout: 60.0,
max: 1
)
for await message in collector {
print("Received: \(message.content)")
}Create persistent interactive UIs with automatic lifecycle management:
let view = BasicView(timeout: 300) { customId, interaction in
if customId == "confirm_button" {
try? await interaction.reply("Confirmed!")
return true // Remove view after use
}
return false
}
client.viewManager?.register(view: view, for: messageId)Organize your bot into modular extensions:
struct ModerationCog: Cog {
func onLoad(client: DiscordClient) async {
print("Moderation module loaded")
// Register commands, set up listeners
}
func onUnload(client: DiscordClient) async {
print("Moderation module unloaded")
}
}
let extensionManager = ExtensionManager()
await extensionManager.load(cog: ModerationCog(), client: client)For large bots, SwiftDisc handles sharding automatically:
let manager = await ShardingGatewayManager(
token: token,
configuration: .init(
shardCount: .automatic,
connectionDelay: .staggered(interval: 1.5)
),
intents: [.guilds, .guildMessages]
)
try await manager.connect()
let health = await manager.healthCheck()
print("Shards: \(health.readyShards)/\(health.totalShards) ready")Connect to voice channels and send audio:
// Enable voice in config
let config = DiscordConfiguration(enableVoiceExperimental: true)
let client = DiscordClient(token: token, configuration: config)
// Join a voice channel
try await client.joinVoice(guildId: guildId, channelId: voiceChannelId)
// Send Opus audio
try await client.playVoiceOpus(guildId: guildId, data: opusPacket)
// Or use an audio source
try await client.play(source: audioSource, guildId: guildId)Send files with messages and interactions:
let file = FileAttachment(
filename: "image.png",
data: imageData,
contentType: "image/png"
)
try await client.sendMessage(
channelId: channelId,
content: "Check out this image!",
files: [file]
)For interaction responses:
try await client.createInteractionResponseWithFiles(
applicationId: appId,
interactionToken: token,
payload: responsePayload,
files: [file]
)SwiftDisc includes helpful utilities for common tasks:
// Mention formatting
Mentions.user(userId) // <@123456>
Mentions.channel(channelId) // <#123456>
Mentions.role(roleId) // <@&123456>
// Custom emoji
EmojiUtils.custom(name: "party", id: emojiId, animated: true)
// Timestamp formatting
DiscordTimestamp.format(date: Date(), style: .relative)
// Escape special characters
MessageFormat.escapeSpecialCharacters(userInput)✅ Send, edit, delete messages
✅ Reactions (add, remove, remove all)
✅ Embeds, components, attachments
✅ Pins, bulk delete
✅ Crosspost, polls
✅ Forward messages
✅ Create, modify, delete channels
✅ Permissions, invites
✅ Webhooks (full CRUD + execute)
✅ Typing indicators
✅ Threads (create, archive, members)
✅ Create, modify, delete guilds
✅ Channels, roles, emojis
✅ Members (add, remove, modify, timeout)
✅ Bans, prune, audit logs
✅ Widget, preview, vanity URL
✅ Templates
✅ Slash commands (global & guild)
✅ Autocomplete
✅ Modals and components
✅ Interaction responses
✅ Follow-up messages
✅ Command localization
✅ Scheduled events
✅ Stage instances
✅ Auto-moderation rules
✅ Application emojis
✅ Role connections (linked roles)
✅ Sticker info (read-only)
❌ Guild sticker creation/modification
❌ Soundboard endpoints
For unsupported endpoints, use the raw HTTP methods: rawGET, rawPOST, rawPATCH, rawDELETE
Check out the Examples directory for complete, runnable examples:
- PingBot.swift - Simple message responder
- CommandFrameworkBot.swift - Command routing with checks
- SlashBot.swift - Slash command handling
- AutocompleteBot.swift - Autocomplete interactions
- ComponentsExample.swift - Buttons, selects, and embeds
- ViewExample.swift - Persistent interactive views
- CogExample.swift - Modular bot architecture
- FileUploadBot.swift - Sending files
- ThreadsAndScheduledEventsBot.swift - Thread and event handling
- VoiceStdin.swift - Voice playback (experimental)
- LinkedRolesBot.swift - Role connections
- Wiki - Setup guides, concepts, and deployment tips
- Examples - Complete working examples
- Discord Server - Get help and discuss the library
- Changelog - Version history and migration guides
# Build the library
swift build
# Run tests
swift test
# With code coverage (macOS)
swift test --enable-code-coverageCI runs on macOS (Xcode 16.4) and Windows (Swift 6.2).
We welcome contributions! Whether it's bug reports, feature requests, or pull requests, we'd love your help making SwiftDisc better.
Before contributing, please:
- Check existing issues and PRs to avoid duplicates
- Read CONTRIBUTING.md for guidelines
- Join our Discord server if you have questions
Expanded Discord API coverage with new gateway events and REST endpoints. See CHANGELOG.md for details.
- Enhanced autocomplete and modal builders
- Additional component types as Discord adds them
- Performance optimizations and caching improvements
- Expanded voice support
Have ideas? Open an issue or join the discussion on Discord!
If you need help or have questions:
- Check the Wiki for guides and documentation
- Browse Examples for code samples
- Join our Discord server for live help
- Search existing issues for similar questions
SwiftDisc is released under the MIT License. See LICENSE for details.
Built with ❤️ using Swift