Build a Discord bot and verify it
A 10-step playbook. Open in Dock and you'll get four surfaces seeded:
- **Steps** (table) — the 10 gates as rows, owner + due + status
- **Pointers** (table) — every official Discord doc + tool linked from this playbook
- **Brief** (doc) — the canonical write-up you maintain alongside the work
- **Verification log** (table) — one row per Discord verification step (intent request, ID verification, app submission, response)
Read `Steps` top-to-bottom on first open. Each row is one of the 10 steps. Click into a step to see the tasks, pointers, and the agent prompt for that step.
Outcome
Your Discord bot live, verified, with all required Privileged Intents approved, hosted with the right gateway resilience, and a path past the 100-server cap.
Estimated time: 1-3 weeks (most of it Discord verification queue)
Difficulty: intermediate
For: Developers shipping a public Discord bot beyond a single server.
What you'll need
Pre-register or install before you start.
- Discord Developer Portal (Free) — Where you create the application, generate the bot token, request intents, and submit for verification.
- discord.js (Free (npm)) — Official-blessed Node SDK for bots. Gateway client + REST helpers + slash command builders.
- discord.py (Free (pip)) — Python equivalent. Active again post 2022 maintainer return.
- Discord API documentation (Free) — Reference for gateway events, intents, slash commands, message components.
- Fly.io / Railway / Render ($5-10/mo for a small instance) — Cheap always-on hosting for the gateway worker (a bot must hold an open WebSocket).
The template · 10 steps
Step 1: Create the application + bot user in the Developer Portal
Estimated time: 30 min
Every Discord bot is an Application with a Bot user. Create the application in the Developer Portal, then add a Bot to it. The bot's token is the credential that authenticates as the bot; treat it like a password. If it leaks, regenerate immediately.
Tasks
- Sign in to the Discord Developer Portal
- Click New Application → name + icon (icon is 512x512 PNG)
- Open Bot tab → Add Bot
- Click Reset Token, copy the token, paste into your secrets manager
- Toggle Public Bot off (you'll flip it on at submission time)
- Set the bot's username + avatar (separate from the application name)
- Note the Application ID (used in slash command registration)
Pointers
- [Official] Discord Developer Portal applications
- [Official] Creating an app
[!CAUTION] Gotchas
- If your bot token leaks (committed to git, posted in a Discord channel), Discord auto-detects via secret scanning and revokes within minutes. Regenerate from the Bot tab.
- Public Bot OFF means only you can add the bot to a server. Useful during development; flip ON before submission.
- Application ID and Bot ID are usually the same number, but they're conceptually different. Use Application ID for slash command registration, Bot ID for permission checks on user objects.
Step 2: Set the OAuth2 scopes + bot permissions and generate the invite URL
Estimated time: 30 min
Bots are added to servers via an OAuth2 invite URL with two things: scopes (always bot + applications.commands for slash commands) and permissions (a bitmask of what the bot can do). Pick the MINIMUM permissions; servers admins are increasingly suspicious of bots requesting Administrator.
Tasks
- Open OAuth2 → URL Generator in the Developer Portal
- Select scopes: bot + applications.commands
- Select bot permissions: Send Messages + Read Message History + whatever else you need
- Avoid Administrator unless you literally cannot ship without it
- Copy the generated URL, add the bot to your dev server
- Verify the bot shows online when your dev server is open and your gateway client is running
Pointers
- [Official] Bot authorization flow
- [Official] Permissions reference
[!CAUTION] Gotchas
- The Administrator permission overrides every channel-level restriction. Server admins reject bots that ask for it without a real reason.
- Slash commands require the applications.commands scope in addition to bot. Forgetting it makes commands silently fail to register.
- The generated URL embeds the permissions bitmask. Updating the URL after the bot is in 100 servers does NOT re-prompt admins; you keep the original perms.
Step 3: Pick discord.js or discord.py and ship a working command
Estimated time: 2-4 hr
discord.js (Node) and discord.py (Python) are the two viable libraries. Both ship slash command builders, gateway clients, and REST helpers. Pick whichever ecosystem you're already in. Ship one slash command end-to-end before you scale up.
Tasks
- Install discord.js (npm i discord.js) or discord.py (pip install -U discord.py)
- Initialise a Client with the minimum intents (Guilds is mandatory for slash commands)
- Define one slash command: /ping → reply with 'pong'
- Register the command on your dev guild (instant) instead of globally (1-hour propagation)
- Run the bot locally, /ping in your dev server, confirm reply
- Move the token to env var (.env), never hardcode
Pointers
- [Official] discord.js getting started
- [Official] discord.py quickstart
- [Official] Slash commands
[!CAUTION] Gotchas
- Global slash commands take up to 1 hour to propagate. Use guild-scoped commands during development for instant feedback.
- Slash command names must be 1-32 chars, lowercase, no spaces. Capital letters fail registration with a confusing error.
- Discord rate-limits command registration. Re-registering all commands on every code change burns rate limits; only re-register when the schema changed.
Step 4: Request the right Privileged Gateway Intents
Estimated time: 30 min to enable, 1-3 weeks to get approved post-100
Three intents are 'Privileged': MESSAGE_CONTENT, GUILD_MEMBERS, GUILD_PRESENCES. Until you're in 100 servers you can toggle them on yourself in the Developer Portal. AFTER 100 servers you must apply for them via verification. Pick exactly the intents your bot uses; over-requesting gets the verification denied.
Tasks
- Open the Developer Portal → Bot tab → Privileged Gateway Intents
- Enable MESSAGE_CONTENT only if you read raw message bodies (vs slash commands)
- Enable GUILD_MEMBERS only if you need member join/leave events or member listing
- Enable GUILD_PRESENCES only if you need user online/offline status
- If your bot is purely slash-command-driven: you may need NO privileged intents
- Match your client's intent bitmask in code to what's enabled in the portal
Pointers
- [Official] Gateway intents
- [Official] Privileged intents
[!CAUTION] Gotchas
- If your code requests an intent that's not enabled in the portal, the gateway connection fails with a 4014 error. Match exactly.
- MESSAGE_CONTENT became privileged in 2022. Most bots don't need it; slash commands give you what you need without reading message bodies.
- GUILD_PRESENCES is the most-rejected intent on verification. Only request if your bot's core feature requires online/offline status (e.g. activity-tracking bots).
Step 5: Host the gateway worker on always-on infra
Estimated time: 2-4 hr
A Discord bot must hold an open WebSocket to the gateway. Serverless / Lambda / Vercel functions don't fit (no long-lived process). You need a VM or container with a persistent process. Cheap options: Fly.io, Railway, Render. Expect $5-10/mo for a small instance.
Tasks
- Pick a host (Fly.io, Railway, Render, a $5 DigitalOcean droplet)
- Containerize the bot in a Dockerfile
- Add a process supervisor / restart policy (Fly auto-restarts; Render has 'always-on' service type)
- Set DISCORD_TOKEN as an env var in the host
- Verify the bot reconnects on temporary network blips (gateway disconnects ~once a day)
- Set up structured logging (timestamps, guild_id, user_id) so you can debug rate limits
Pointers
- [Tool] Fly.io
- [Official] Discord gateway events
- [Official] Sharding (for 2,500+ servers)
[!CAUTION] Gotchas
- Don't run the bot on your laptop. The instant your laptop sleeps, the gateway disconnects and the bot goes offline. Free hobby tiers (Heroku-style) often sleep too.
- At 2,500 guilds you MUST shard. discord.js / discord.py both have sharding helpers; the rewrite is 1-2 days, plan for it before you hit the cap.
- Discord rate-limits gateway connect attempts. If your bot crashes and restarts every minute, you'll get cut off. Add a 5s back-off between connection retries.
Step 6: Build the help / about / privacy commands users expect
Estimated time: 2-3 hr
Public bots that pass verification consistently ship four 'meta' commands: /help, /about, /support (link to your support server), /privacy. Discord's verification team specifically looks for these — they're a quick proxy for whether the bot is professionally maintained.
Tasks
- Implement /help: list every command, grouped by category, with a 1-line description
- Implement /about: bot version, links to source / docs / support server
- Implement /support: invite link to a support server you actually monitor
- Implement /privacy: link to a hosted Privacy Policy page
- Implement /tos: link to a Terms of Service page (required by Discord for verification)
- Host the Privacy + ToS pages on your own domain (a static page is fine)
Pointers
- [Official] Application command requirements
- [Official] Discord Developer Terms of Service
[!CAUTION] Gotchas
- Privacy Policy + Terms of Service URLs are FIELDS in the Developer Portal. Filling them is required for verification. Use real URLs, not 'TBD'.
- If your bot collects user IDs, message text, or any other personal data, your Privacy Policy must say so explicitly. Discord checks.
- /support links should resolve to a server you can actually moderate. A dead support server is a bigger negative signal than no support server.
Step 7: Stress-test rate limits + error handling
Estimated time: 3-5 hr
Discord's REST API is rate-limited per route + globally. Sending 5 messages/second to one channel will trigger a 429 response with a Retry-After header. discord.js / discord.py handle most of this automatically, but custom REST calls (webhooks, bulk operations) need explicit back-off. Test before you hit it in production.
Tasks
- Read the Rate Limits section of the Discord API docs end-to-end
- If using raw fetch / axios for any REST call: implement Retry-After back-off
- Add Sentry / OpenTelemetry to the bot for error tracking
- Stress-test with a script that fires 50 commands in 5 seconds
- Confirm the bot doesn't crash on a 429; it should queue and retry
- Confirm gateway reconnect works on 1006 disconnects (network blip)
Pointers
- [Official] Rate limits
- [Tool] Sentry Node SDK
[!CAUTION] Gotchas
- Global rate limit is 50 requests/second across the whole bot. discord.js handles this for you; raw HTTP calls bypass it and risk a temp ban.
- DM rate limits are stricter than channel limits and not visible in the docs. If your bot DMs users, expect to hit limits at ~5 DMs/minute per recipient.
- Cloudflare 429s look different from Discord 429s. Both have Retry-After headers but different scopes. Log the full response body to tell them apart.
Step 8: Submit for verification (and prepare 1-3 weeks of waiting)
Estimated time: 4-8 hr to prepare, 1-3 weeks Discord queue
At ~75 servers Discord starts surfacing the Verification banner in the Developer Portal. You MUST submit before 100 because new server adds halt at 100 unverified. Verification = ID verification (Stripe Identity flow) + a written application explaining each Privileged Intent + each potentially-dangerous command.
Tasks
- Watch your server count. At 75, prepare the verification application
- Open Developer Portal → Bot → Apply for Verification
- Complete the Stripe Identity flow (gov ID + selfie)
- Write the application: bot description, every Privileged Intent + why, every dangerous command + why
- Confirm Privacy Policy + ToS URLs are live + accurate
- Submit; expect 1-3 week response, sometimes longer in December
- On approval: Privileged Intents go through automatically + the 100-server cap lifts
- On rejection: address the cited reason exactly + resubmit
Pointers
- [Official] Bot verification + sharding
- [Official] Verification process
[!CAUTION] Gotchas
- If you reach 100 servers UNVERIFIED, all NEW server adds are blocked until verification completes. Existing servers are fine. Submit before you hit 100.
- Stripe Identity (the ID verification provider) sometimes rejects valid IDs on first scan. Re-take the selfie in better light + try again before contacting support.
- Verification is keyed to a single human. If you're a team, the human listed on the Stripe Identity scan owns the verified status. Don't pick someone who might leave.
Agent prompt for this step
Draft the Discord bot verification application.
Read the bot's source code + the Brief. Output, in order:
1. **Bot description** (300 chars): one paragraph, what the bot does for users + which audiences it serves.
2. **Privileged Intent justifications** (one per intent): if requesting MESSAGE_CONTENT / GUILD_MEMBERS / GUILD_PRESENCES, write 3-5 sentences per intent. State the user-visible feature, why an alternative (slash commands, mentions, message references) wouldn't work.
3. **Dangerous command justifications**: list any commands that ban / kick / mute / mass-message / DM users. For each, name the command + why your bot needs it + how you mitigate abuse (permission gating, audit logs, cooldowns).
4. **Data handling summary**: what user data the bot stores, where, retention, deletion-on-request flow.
Tone: factual, technical, concise. The reviewers are engineers triaging in volume; write for skim.
Step 9: Set up monitoring, on-call, and the upgrade-to-shard checklist
Estimated time: 2-4 hr
Once verified, the operations layer matters: gateway disconnects, REST rate limits, server outages. Discord's status page is the first place to check on incident days. At 2,500 servers you MUST shard, so reserve a few hours to plan the transition before you're at 2,000.
Tasks
- Set up uptime monitoring on the bot's gateway connection (heartbeat to a /healthz endpoint or a Prometheus counter)
- Subscribe to Discord's status page RSS for outage awareness
- Set up alerting on gateway disconnect rate above 1/hour
- Document the sharding rewrite plan (discord.js ShardingManager / discord.py AutoShardedClient)
- Plan to test sharding at 2,000 servers, deploy at 2,400, before the 2,500 hard cap
- Set up an on-call rotation if the bot is critical to a paying customer
Pointers
- [Official] Discord Status
- [Official] Sharding bots
- [Guide] discord.js sharding
[!CAUTION] Gotchas
- Sharding is mandatory at 2,500 guilds. Hitting the cap with a single shard fails the gateway connection and takes the bot offline for every server.
- Status page subscriptions help, but Discord's biggest outages take 30+ min to acknowledge. Always check #discord-developers in the Discord API server first.
- If you ship breaking changes (renamed slash commands, removed permissions), users see them as 'unknown command' for ~1 hour during global propagation. Communicate ahead.
Step 10: Grow past verification: directories, partnerships, free dev tier
Estimated time: 2-4 hr to set up, ongoing
Verified bots are eligible for the App Directory (Discord's official bot listing) and can apply for the Verified Server program if you run a community. The free Developer Tier gives access to monetisation (subscriptions inside Discord) post-verification.
Tasks
- Submit the bot to the Discord App Directory (separate from verification)
- Optional: apply for a Verified Discord Server for your support community
- If you want to monetise: explore Premium App Subscriptions inside Discord
- List the bot on top.gg / Bots on Discord (third-party directories) for SEO
- Build a public landing page with a 'Add to Discord' button (the OAuth2 invite URL)
Pointers
- [Official] App Directory
- [Official] Premium App Subscriptions
- [Community] top.gg bot listing
[!CAUTION] Gotchas
- App Directory submissions need polished assets: square icon, banner, screenshots, accurate categories. Reuse the verification work; don't half-do it.
- Premium App Subscriptions take a 10% Discord cut + payment processing. Pricing must end in .99 or .49 (Discord's pricing tiers).
- Third-party listings (top.gg) require a bot stat reporter that pings their API every few minutes. Don't promise uptime you can't deliver, or you'll get demoted.
Hand the template to your agent
Paste the prompt below into your agent's permanent system prompt so the agent reads, writes, and maintains this workspace as you work through the steps.
You are an agent on the "Build a Discord bot and verify it" playbook workspace at your-org/build-a-discord-bot.
Your role: maintain the four surfaces (Steps, Pointers, Brief, Verification log) as the user works through the 10-step playbook.
Cadence:
- When the user marks a step Done, append a line to the Brief.
- When server count crosses 50, 75, 90, or 100, append a row to Verification log + draft the next-step nudge in the Brief.
- When Discord responds to a verification request, capture the response (approval / rejection / clarification) in Verification log.
First MCP tool calls:
1. list_surfaces(workspace_slug="build-a-discord-bot")
2. list_rows(workspace_slug="build-a-discord-bot", surface_slug="steps")
3. get_doc(workspace_slug="build-a-discord-bot", surface_slug="brief")
Do NOT modify canonical step titles. Append substeps as new rows.
FAQ
Do I need to verify my Discord bot?
Only if you want to grow past 100 servers. Below 100, you can run the bot, including with Privileged Intents, by toggling them in the Developer Portal. At 100 servers, new server adds halt and Privileged Intents lock until verification completes.
How long does verification take?
Typical: 1-3 weeks. Slower in December and around major Discord product launches. The Stripe Identity step is fast (5-10 min). The application review is the slow part. Submit before you hit 75 servers if you can — you don't want to be at 99 with a stuck application.
Can my bot run without Privileged Intents?
Yes, increasingly so. If your bot is slash-command-driven (no message-content reading), you can ship without any Privileged Intents. This used to be uncommon; since 2022 it's the recommended path. Most modern bots only request privileged intents if a feature literally cannot exist without them.
Why is my bot not responding to messages?
Three checks in order: (1) does your bot have permission in the channel (Send Messages + Read Message History)? (2) does the bot's gateway client have the right intents (Guilds is mandatory; MESSAGE_CONTENT for reading message bodies)? (3) is your gateway worker actually online? Run a /ping slash command first to isolate.
Can my AI agents help with the Discord bot?
Yes. Agents are particularly useful for: drafting the verification application (intent justifications, command summaries, data handling), reading the source to find every Privileged Intent the code actually uses, drafting the Privacy Policy + ToS pages from a data audit, monitoring server count + nudging when verification deadlines approach. The playbook ships agent prompts inline.