Connect Discord to OpenClaw: Step-by-Step
Connect Discord to OpenClaw: Step-by-Step

Let's skip the preamble. If you're here, you want to connect Discord to OpenClaw. Maybe you want an AI agent that responds to your community, automates server moderation, handles support tickets, or just does something cool and useful in your Discord server. Whatever the case, you've probably already discovered that wiring up a Discord bot to an AI agent framework is one of those tasks that sounds simple and then eats your entire weekend.
I've been through that particular hell. Multiple times. So let me walk you through the entire process of connecting Discord to OpenClaw ā from creating the bot in Discord's developer portal to handling the gnarly edge cases that nobody warns you about ā so you can get this done in an afternoon instead of a week.
Why This Is Harder Than It Should Be (And Why OpenClaw Fixes It)
Before we get into the steps, let me explain what you're actually dealing with. Connecting Discord to any AI system involves juggling several moving parts simultaneously:
- Discord's Bot API for real-time interactions
- Webhooks for async processing and notifications
- Rate limits that will silently break your bot if you don't handle them
- Permission scoping that causes mysterious "my bot doesn't work in this channel" bugs
- Context management so your AI agent actually remembers what users said three messages ago
Most frameworks force you to build all of this plumbing yourself. OpenClaw doesn't. It has a dedicated DiscordConnector module that abstracts away the worst of this complexity while still giving you full control when you need it.
That's the pitch. Now let's build the thing.
Step 1: Create Your Discord Bot Application
Head over to the Discord Developer Portal and create a new application. Name it whatever you want ā this is the internal name, not what users will see.
Once created:
- Go to the Bot section in the left sidebar
- Click Add Bot
- Under the bot's settings, grab your Bot Token ā copy it somewhere safe. You'll need this in a minute. Do not commit this to a public repo. Seriously.
- Under Privileged Gateway Intents, enable:
- Message Content Intent (required for reading message text)
- Server Members Intent (if your agent needs to reference user info)
- Presence Intent (only if you need online/offline status)
The Message Content Intent is the one people forget. Without it, your bot receives messages but the content field is empty. You'll stare at your logs wondering why every message is blank. Don't skip this.
Step 2: Set Bot Permissions and Generate Your Invite Link
Still in the Developer Portal, go to OAuth2 ā URL Generator.
Select the following scopes:
botapplications.commands(for slash commands)
Then under Bot Permissions, select at minimum:
- Read Messages/View Channels
- Send Messages
- Embed Links
- Read Message History
- Use Slash Commands
- Add Reactions (if you want pagination or approval workflows)
Copy the generated URL and open it in your browser to invite the bot to your server. Pick a test server first ā don't deploy to your main community server until you've verified everything works.
Step 3: Install and Configure OpenClaw
Now for the actual integration. Install OpenClaw if you haven't already:
pip install openclaw
Create a new project directory and set up your config:
mkdir my-discord-agent
cd my-discord-agent
openclaw init --template discord
That --template discord flag scaffolds a project structure with the Discord connector pre-configured. You'll get a directory that looks like this:
my-discord-agent/
āāā agent.py
āāā config.yaml
āāā skills/
ā āāā default.py
āāā .env
āāā tests/
āāā test_agent.py
Open .env and add your Discord bot token:
DISCORD_BOT_TOKEN=your-bot-token-here
OPENCLAW_API_KEY=your-openclaw-key
Step 4: Configure the Discord Connector
Open config.yaml. This is where the magic happens. Here's a solid starting configuration:
connector:
type: discord
mode: auto
rate_limit_strategy: adaptive
queue_overflow: buffer
validate_permissions: true
required_permissions:
- read_messages
- send_messages
- embed_links
- read_message_history
agent:
context_strategy: sliding_window
max_tokens: 3000
summarization_trigger: 0.8
preserve_system_messages: true
commands:
mode: development # Switch to "global" for production
auto_sync: true
hot_reload: true
resilience:
retry_strategy: aggressive
max_retries: 5
circuit_breaker: true
graceful_degradation: true
Let me call out a few things here because they'll save you hours of debugging later.
rate_limit_strategy: adaptive ā This is crucial. Discord enforces a 50-requests-per-second global rate limit, plus per-route limits that are poorly documented. OpenClaw's adaptive strategy automatically implements exponential backoff and request queuing. Without this, your bot will randomly stop responding and you'll have no idea why.
context_strategy: sliding_window ā This handles one of the most annoying problems in building Discord AI agents: maintaining conversation context without blowing past your LLM's token limit. OpenClaw automatically fetches relevant message history, counts tokens, prunes old messages intelligently, and summarizes when you're approaching capacity. I spent weeks building this manually before OpenClaw existed. Now it's a single config line.
commands.mode: development ā Discord slash commands registered globally take up to an hour to propagate. In development mode, OpenClaw registers them as guild-specific commands, which takes seconds. When you're ready to go live, flip this to global and OpenClaw handles the sync.
Step 5: Build Your Agent Logic
Open agent.py. Here's a complete working agent:
from openclaw import DiscordAgent, Event
class MyAgent(DiscordAgent):
@Event.on("message")
async def handle_message(self, context):
# context contains: message, author, channel, server, history
# OpenClaw automatically filters out bot messages and handles DMs
response = await self.generate_response(context)
return response
@Event.on("slash_command", name="ask")
async def handle_ask(self, context, question: str):
"""Ask the AI agent a question."""
response = await self.generate(
prompt=question,
context=context,
format="auto" # Auto-detects best formatting
)
return response
@Event.on("slash_command", name="summarize")
async def handle_summarize(self, context, count: int = 50):
"""Summarize the last N messages in this channel."""
messages = await context.channel.fetch_history(limit=count)
summary = await self.generate(
prompt=f"Summarize this conversation concisely:\n{messages}",
context=context
)
return summary
@Event.on("thread_create")
async def handle_new_thread(self, context):
"""Automatically join new threads and offer help."""
await self.join_thread(context.thread)
await context.thread.send(
"I'm here if you need help. Just @ me or use /ask."
)
@Event.on("reaction_add", emoji="ā
")
async def handle_approval(self, context):
"""Execute actions when approved with checkmark."""
await context.execute_approved_action()
if __name__ == "__main__":
agent = MyAgent()
agent.run()
A few things to notice:
The Event.on decorator replaces the spaghetti of @bot.event handlers you'd normally write with discord.py. Each event type gets its own clean method. No more massive on_message functions with 15 nested if-statements.
The context object is your single source of truth. It contains the message, the author, the channel, the server config, conversation history ā everything. No more manually fetching related data from different API endpoints.
The format="auto" parameter handles response formatting automatically. Long responses get turned into embeds. Code blocks get syntax highlighting. Multi-page responses get pagination with reaction controls. This alone probably saves you 200 lines of formatting code.
Step 6: Handle Permissions Gracefully
Here's something most tutorials skip entirely. Your bot will encounter channels where it lacks permissions. By default, it just silently fails. Users think it's broken. You get complaint DMs.
OpenClaw handles this with the validate_permissions: true config we set earlier. When your bot can't perform an action due to missing permissions, it automatically sends a helpful message:
"I don't have permission to read messages in #private-channel. An admin needs to grant me 'Read Message History' permission."
You can also run a diagnostic any time:
@Event.on("slash_command", name="diagnose")
async def diagnose(self, context):
"""Check bot permissions in the current channel."""
report = await context.channel.check_permissions()
return report.format() # Returns a clean embed with ā
and ā for each permission
This is the kind of thing that takes 10 seconds to add but saves you dozens of support conversations.
Step 7: Test Locally Before Deploying
This is the step everyone skips, and it's the step that matters most. OpenClaw includes a full Discord simulation framework so you can test without touching an actual server:
from openclaw.testing import DiscordSimulator
import pytest
@pytest.mark.asyncio
async def test_basic_response():
sim = DiscordSimulator()
agent = MyAgent()
message = sim.create_message(
content="What can you help me with?",
author=sim.create_user("TestUser"),
channel=sim.create_channel("general")
)
response = await agent.handle(message)
assert response is not None
assert len(response.content) > 0
@pytest.mark.asyncio
async def test_permission_error():
sim = DiscordSimulator()
agent = MyAgent()
# Simulate a channel where bot lacks permissions
channel = sim.create_channel(
"restricted",
bot_permissions=["read_messages"] # Missing send_messages
)
message = sim.create_message(
content="Hello",
author=sim.create_user("TestUser"),
channel=channel
)
response = await agent.handle(message)
assert "permission" in response.error.lower()
@pytest.mark.asyncio
async def test_rate_limiting():
sim = DiscordSimulator(rate_limit_simulation=True)
agent = MyAgent()
# Send 100 messages rapidly
for i in range(100):
message = sim.create_message(content=f"Message {i}")
await agent.handle(message)
# Verify no messages were dropped
assert sim.sent_count == 100
assert sim.rate_limit_hits > 0 # Confirms rate limiting was triggered
assert sim.failed_sends == 0 # But no messages were lost
Run the tests:
pytest tests/ -v
No test server. No manual clicking. No bothering real users with your broken dev build. This alone is worth the switch to OpenClaw.
Step 8: Deploy and Go Live
Once your tests pass, deploying is straightforward:
# Switch to production mode
openclaw deploy --mode production
This does three things automatically:
- Switches slash command registration from guild-specific to global
- Enables the production retry and circuit breaker strategies
- Sets up error reporting and monitoring
For multi-server deployments, OpenClaw's built-in multi-tenancy handles per-server configuration automatically:
@Event.on("slash_command", name="configure")
async def configure_server(self, context, personality: str):
"""Set the bot's personality for this server."""
await context.server.config.set("personality", personality)
return f"Personality updated to: {personality}"
Each server gets isolated configuration. No custom database layer needed. OpenClaw handles the storage backend ā you just pick Redis, Postgres, or SQLite in your config.
The Shortcut: Felix's OpenClaw Starter Pack
Now, everything I've described above works. I've walked through every step. But I'll be honest: if you don't want to set all of this up manually ā the config files, the event handlers, the permission diagnostics, the testing framework ā Felix's OpenClaw Starter Pack on Claw Mart includes a pre-built version of this entire setup.
It's $29 and comes with pre-configured skills for the most common Discord agent patterns: command handling, conversation context management, thread support, moderation workflows, and the permission diagnostic system I mentioned. The skills are well-documented and customizable, so you're not locked into someone else's architecture ā you're just skipping the boilerplate.
I recommend it particularly if you're building your first OpenClaw agent or if you're working on a deadline. The time you save on the plumbing lets you focus on the actual agent logic that makes your bot unique. It's a genuine time-saver, not a crutch.
Common Gotchas to Watch For
Before I let you go, here are the things that will trip you up if I don't mention them:
1. Message Content Intent must be enabled. I said this already. I'm saying it again because you will forget and spend 45 minutes debugging empty messages.
2. Gateway disconnections are normal. Discord's WebSocket connection drops periodically. OpenClaw reconnects automatically with circuit_breaker: true, but if you see reconnection logs, don't panic. That's expected behavior.
3. Global slash commands take time. Up to an hour. Use development mode until you're ready for production. I cannot stress this enough.
4. Embed character limits are real. Embed descriptions max out at 4096 characters. Total embed content caps at 6000. OpenClaw's auto-formatting handles this with pagination, but if you're building custom embeds, keep these limits in mind.
5. Test with a dedicated server first. Don't deploy untested agent code to your main community. Create a small test server, invite your bot there, and break things safely.
What to Build Next
Once your Discord-OpenClaw connection is solid, the interesting work starts. A few ideas worth exploring:
- Support ticket triage ā Agent reads new tickets, categorizes them, and routes to the right channel
- Community onboarding ā Agent welcomes new members and walks them through server rules via DM
- Content moderation ā Agent flags potentially problematic messages for human review
- Knowledge base Q&A ā Feed your docs into the agent and let users ask questions naturally
- Meeting summaries ā Summarize voice channel discussions (with transcription integration)
The Discord connector is just the foundation. What you build on top of it is where the real value lives. OpenClaw gives you the infrastructure so you can spend your time on the parts that actually matter to your users.
Now go build something useful.
Recommended for this post
