A Discord bot spec is a short document that says what the bot must do, for whom, in which server, with which data and outside systems, and how you will check that it works. It has 12 parts: goal, server context, user stories, commands, events, stored data, integrations, permissions, edge cases, an acceptance checklist, ownership, and budget and deadline.
A good spec lets two developers quote the same bot, and lets you and the one you hire agree on when it's finished. Copy the template below, fill in what you know and write "not sure" where you don't. There is a filled-in example for a league check-in bot after it.
Why a spec saves you money
Disputes on bot projects are often about whether something counts as done, not about code quality. One public review of a custom Discord bot gig on Fiverr, for a three-month project, says the developer "tried to move the project to the next milestone" before the current one was complete. A checklist both sides accepted up front settles that kind of argument quickly.
A spec also gets you comparable quotes. "A league bot with check-ins" can mean a small weekend job or a multi-week system with a database and a Google Sheets sync. Quotes written from the same document can be compared. My Discord bot cost guide shows what drives those numbers.
It also surfaces Discord's limits before they cost you a rebuild.
The 12 sections of a Discord bot spec
1. Goal
One or two sentences on the problem and what changes when the bot exists. Add one or two signs that it's working, such as "referees stop pinging captains by hand" or "no ticket waits longer than 12 hours for a first reply". The goal is what you fall back on when a detail is unclear halfway through the build.
2. Server context
How many members, which channels and roles the bot touches, and what other bots already run there. Say whether the bot serves one server or many: a bot in several servers stores its settings per server, and a bot can't grow past 100 servers until Discord verifies it.
Give the numbers that grow, such as teams per season or tickets per week, because they decide how the bot is built. For Rivals League, the provisioning scripts build the server from the roster: 7 divisions, 18 groups and 154 teams, each with its own role and private voice channel. That is 154 roles and 154 channels before a single staff role or division channel, a real share of Discord's caps of 250 roles and 500 channels. More about that project on the work page.
3. User stories for each role
For each group that touches the bot (members, staff such as moderators or referees, and admins), write what they want to do and why: "As a captain, I want to check in my team with one button, so the referee knows we're ready without me pinging anyone." The "so that" part tells the developer what matters when two designs are possible.
4. Commands
A table with the command, its options, who may use it, what happens, and who sees the reply (only the person who ran it, or the whole channel). Discord allows up to 25 options per command and up to 25 fixed choices per option, so a "pick your game" option with 40 games needs autocomplete instead. The slash commands guide shows how commands work from the member's side.
5. Events and automations
Everything the bot does without a command: when someone joins, on a schedule, when a button is pressed, or when something happens outside Discord. Write each one as "when X happens, the bot does Y, in channel Z", and give scheduled times with a time zone.
This section decides which gateway intents the bot needs. Reacting to member joins needs the Server Members intent, and reading what people write needs the Message Content intent. Both are privileged. As of September 2026, once an app reaches 10,000 unique users across its servers, Discord has to approve them in a review that is repeated every year. Button presses and slash commands don't need either, because each interaction already includes the member's roles and permissions.
6. Data the bot stores
List what the bot keeps, why, for how long and who can see it. Discord's Developer Policy says apps may use API data only as needed for the app's stated function, so "we might want it later" is not a reason to store something.
Discord's own audit log keeps entries for 45 days. If you need to prove who checked in, who closed a ticket or who changed a role months ago, the bot has to keep its own log.
7. Integrations and credentials
For each outside system: what data moves in which direction, how the bot connects (API, webhook, a shared sheet), whose account it runs under and who supplies the key. Put the accounts in your organization's name, not the developer's. If members will pay for anything through the bot, say so here: Discord's Developer Policy has monetization rules, and the developer should check how they apply to your setup.
8. Permissions
Which permissions the bot needs, which roles it assigns, who can run staff and admin commands, and which channels it must stay out of. One rule catches many projects: a bot can only assign roles that sit below its own highest role in the server's role list. The permissions calculator turns a list of permissions into the number the invite link needs.
9. Edge cases
What should happen when things go sideways: a member leaves halfway through a flow, two people press the same button at the same moment, the bot restarts, an outside API is slow or down, daylight saving time changes mid-season. You don't need every answer. Listing them matters, because each one is a decision the developer would otherwise make for you.
10. Acceptance checklist
Statements that are either true or false when you test the bot: "A captain from another team gets a private refusal", not "check-in works well". This is the list you tick off before paying the final invoice, so every must-have feature should appear in it.
11. Hosting and ownership
Who owns the Discord application, the code repository and the hosting account, and who fixes things after launch. My recommendation for any paid build: create the application in a Discord developer team that you own, and add the developer with the Developer role. Discord's docs say that role can reset the bot token and take limited actions such as configuring interaction endpoints, but can't manage the team or take destructive actions. Teams require two-factor authentication, and an app transferred to a team can't be transferred back.
12. Budget and timeline
A budget range, a go-live date and why that date matters (season start, a launch, an event). Say what could ship in a second phase, and list what is out of scope. An explicit out-of-scope list is the cheapest protection against scope arguments.
Discord limits to check before you write
These are the Discord limits that shape a bot's design most directly, from Discord's documentation and support pages as of September 2026.
| Limit | Value | Why it matters in a spec |
|---|---|---|
| Roles per server | 250 | A role per team, per rank or per game adds up fast |
| Channels per server | 500, categories included | A text and a voice channel per team halves how many teams fit |
| Channels per category | 50 | Big leagues need several categories, or threads instead of channels |
| Audit log history | 45 days | Keep your own log if you need older records |
| Options per slash command | 25 | Long forms belong in a modal or several steps |
| Fixed choices per option | 25 | Longer lists need autocomplete |
| Slash commands per app | 100 global, plus 100 per server | Rarely a problem |
| Time to answer an interaction | 3 seconds, or defer and finish within 15 minutes | Slow outside APIs need a "thinking" state |
| Message text | 2,000 characters | Long standings or rules go in embeds or several messages |
| Embeds | Up to 10 per message, 6,000 characters in total, 25 fields each | A 48-team table with one field per team doesn't fit in one embed |
| Buttons | 5 per row, 5 rows per classic message | Per-match or per-team buttons may need several messages |
| Privileged intents | Discord review once the app reaches 10,000 users, repeated yearly | Member-join and message-reading features depend on it |
| Servers per app | Verification needed to grow past 100 | A public bot needs time for verification before launch |
The counts matter in practice. For a Swedish online esports league, a bot I co-built with another developer created 138 channels and 123 roles for 131 teams in under half an hour. Counting them in the spec is how you find out whether a design fits in one server. The details are on the work page.
The template
It's plain Markdown: copy it into Notion, GitHub, Google Docs or a text file, delete the hints you don't need and fill in the rest.
# Discord bot spec: [bot name]
Version: 1 | Date: YYYY-MM-DD | Contact: [name, Discord username, email]
## 1. Goal
- The problem today:
- What changes when the bot exists:
- How we'll know it works (one or two signs):
## 2. Server context
- Servers the bot runs in: [one / several / public bot]
- Members: [about how many] | Active on a busy day: [about how many]
- Channels the bot uses: [#channel: what for]
- Roles involved: [role: who has it, about how many people]
- Other bots in the server and what they do:
- Numbers that grow (teams per season, tickets per week):
## 3. User stories
Members:
- As a [member type], I want to [action], so that [reason].
Staff:
- As a [moderator / referee / support agent], I want to [action], so that [reason].
Admins:
- As an admin, I want to [action], so that [reason].
## 4. Commands
| Command and options | Who can use it | What happens | Reply visible to |
|---|---|---|---|
| /command option | everyone / role | | only the user / the channel |
## 5. Events and automations
- When [something happens], the bot [does what] in [where].
- Every [day and time, with time zone], the bot [does what].
- When [outside system] sends [event], the bot [does what].
## 6. Data the bot stores
| What | Why it's needed | How long it's kept | Who can see it |
|---|---|---|---|
| | | | |
- Does the bot need to read what members write? [no / yes, because]
- Does it need to know when members join, leave or change roles? [no / yes, because]
## 7. Integrations and credentials
| System | Data in or out | How it connects | Account owner | Who supplies the key |
|---|---|---|---|---|
| | | API / webhook / sheet | | |
- Do members pay for anything through the bot? [no / yes: what and how]
## 8. Permissions
- Permissions the bot needs:
- Roles the bot assigns (its own role must sit above them):
- Who can run staff and admin commands:
- Channels the bot must not touch:
## 9. Edge cases
- A member leaves halfway through a flow:
- Two people press the same button at once:
- The bot restarts (must old buttons keep working?):
- An outside system is slow or down:
- Time zones and daylight saving:
- Limits we may hit as we grow (roles, channels, message length):
## 10. Acceptance checklist
- [ ] [A statement that is true or false when tested]
- [ ] [One line per must-have feature]
- [ ] Setup docs let another developer run the bot from scratch
## 11. Hosting and ownership
- Discord application owned by: [our developer team / our account]
- Code repository owned by:
- Hosting: [who runs it, where, who pays]
- After launch: [who fixes bugs, who updates the bot when Discord changes]
## 12. Budget and timeline
- Budget range:
- Must be live by, and why:
- Phase 1 must include:
- Out of scope for this project:Example spec: a league check-in bot
This example is made up. The Northwind League, its numbers and its dates are invented to show the level of detail a developer needs; it is not a client project.
Goal
Match nights are Tuesdays and Thursdays at 8:00 PM US Eastern. Referees ping captains by hand to find out who's ready, and matches start late. The bot gives every match a check-in window, reminds captains who haven't checked in, and gives referees one summary at start time. Signs it works: no check-in DMs from referees, and a timestamped record behind every forfeit.
Server context
- One server, about 1,400 members.
- 48 teams in 4 divisions of 12, so 24 matches on a full match night. Next season may have 64 teams.
- Channels:
#check-in(the bot posts here),#referee-desk(staff only, the bot posts summaries),#announcements(the bot never posts here). - Roles: Admin (3 people), Referee (8), Captain (one or two per team), and 48 team roles that admins create each season. 64 team roles next season still fits under the 250-role cap.
User stories
- As a captain, I want to check in my team with one button during the 30 minutes before our match, so the referee knows we're ready.
- As a captain, I want a reminder 10 minutes before start if we haven't checked in, so we don't forfeit by accident.
- As a referee, I want one list of tonight's matches with their check-in state, so I can start matches on time.
- As a referee, I want to check a team in by hand with a reason, for when a captain's Discord is down.
- As an admin, I want to load a night's matches from our schedule sheet and see a preview first, so a sheet mistake doesn't go out.
Commands
Captains check in with a button, not a command.
| Command and options | Who can use it | What happens | Reply visible to |
|---|---|---|---|
/matches |
Everyone | Lists tonight's matches, start times (as Discord timestamps) and check-in state | Only the user |
/checkin-override match team reason |
Referee, Admin | Marks the team as checked in and records who did it and why | Only the user; a line goes to #referee-desk |
/schedule-import date |
Admin | Reads that date's matches from the sheet and shows a preview with Confirm and Cancel buttons | Only the user |
/schedule-cancel match |
Admin | Takes a postponed match out of check-in and marks its message "Postponed" | Only the user |
/checkin-settings window reminder |
Admin | Sets the window (15 to 60 minutes) and the reminder (5 to 30 minutes before start) | Only the user |
Events and automations
- Window opens (default: 30 minutes before start): one message per match in
#check-inwith both team names, the start time and a Check in button for each team. - Button press: only a member with the Captain role and that team's role can check the team in. Anyone else gets a private refusal. The message updates to show who checked in and when.
- Reminder (default: 10 minutes before start): the bot mentions the captains of teams that haven't checked in, in
#check-in. - Start time: buttons lock, and
#referee-deskgets a summary with checked-in teams (and times) and missing teams. The bot never decides a forfeit; referees do.
Data the bot stores
| What | Why it's needed | How long it's kept | Who can see it |
|---|---|---|---|
| Matches: ID, teams, division, start time in UTC | To run the windows | The season plus 90 days | Everyone, through /matches |
| Check-ins: team, Discord user ID, time, override by, reason | The record for forfeit disputes | The season plus 90 days, then exported to CSV for admins and deleted | Referees and admins |
| Settings: window, reminder, channel and role IDs | Configuration | While the bot runs | Admins |
The bot doesn't read message content and doesn't need member join or leave events; the roles it checks arrive with each button press.
Integrations and credentials
| System | Data in or out | How it connects | Account owner | Who supplies the key |
|---|---|---|---|---|
| Google Sheets, "Season schedule" tab | In: date, time (US Eastern), division, home team, away team, match ID | A Google service account with read-only access to that one sheet | The league's Google account | A league admin shares the sheet with the service account |
Members don't pay for anything through the bot. start.gg results are out of scope for this project.
Permissions
- The bot needs View Channels, Send Messages, Embed Links and Read Message History in
#check-inand#referee-desk, and nothing elsewhere. - It assigns no roles.
- Referee and admin commands check roles in code, and server admins also hide them from other members in the server's command permission settings.
Edge cases
- A captain from the other team, or a player who isn't a captain, presses a button: private refusal, nothing changes.
- Both captains of a team press at the same moment: one check-in is recorded, no duplicate messages.
- The bot restarts during a window: buttons keep working and no check-ins are lost, because the state lives in the database.
- A match is rescheduled after import: the admin cancels it and imports the new date; the old message says "Postponed".
- The sheet can't be reached: the import fails with a clear message and nothing changes.
- US clocks go forward on March 14, 2027, mid-season. Sheet times are US Eastern, and the bot converts each match to UTC using that match's own date.
Acceptance checklist
-
/schedule-importfor a test date shows a preview that matches the sheet row by row, and Cancel posts nothing. - Check-in messages appear 30 minutes before each start time, within one minute.
- A captain of the right team can check in; anyone else gets a private refusal.
- The reminder mentions only captains of teams that haven't checked in.
- At start time the buttons lock and
#referee-deskgets the summary. - An override records who made it and why.
- Restarting the bot during a window loses no check-ins, and the buttons still work.
- The Tuesday, March 16, 2027 match (first match night after the clock change) opens at 7:30 PM Eastern.
- With the setup docs, another developer can run the bot on a fresh test server.
Hosting and ownership
- The Discord application lives in the league's developer team, with the developer as a member with the Developer role. The code lives in the league's GitHub organization.
- The developer hosts the bot for the first season; the league reviews that before the next one.
- After launch: 30 days of bug fixes, then a monthly maintenance plan or hourly work.
Budget and timeline
- Budget: $1,500 to $2,000.
- Live by Tuesday, January 12, 2027, the first match night of the season. A dry run with referees on a test server the week before.
- Phase 1: everything above. Phase 2, not in this project: start.gg results and standings.
For scale: with my prices as of September 2026, a bot like this (a database, a handful of commands and one Google Sheets integration) usually lands in the Community tier, from $1,490. The pricing page lists what each tier includes. If your league wants something like it, the esports and league bots page shows what else these bots usually do.
What to leave out
- The technology, unless you have a reason. "Must run on our existing server" is a requirement; "use Python" usually isn't.
- Features nobody asked for yet. Put them in the phase 2 list, where they don't raise the price of phase 1.
- Anything that breaks Discord's rules. I decline selfbots, fake members and bots that copy messages out of servers you don't own, and so should any developer you hire. The Discord Developer Policy also rules out contacting members without their permission, so "DM everyone who joins with our offer" is out too.
Send it, or write it with me
If you've filled in the template, send it with a short brief and I'll reply with questions or a fixed quote.
If you'd rather not write it alone, that is what the Bot Blueprint is for: a 45-minute call about your server and workflows, then a written spec with an acceptance checklist we both sign off on, and a fixed price and delivery date for the build. It costs $249 and is credited in full if you order the build within 30 days. You can book a Blueprint. If you're still choosing who to hire, how to hire a Discord bot developer covers the questions to ask.