Skip to content

Create the Discord app

Every judgebot is its own Discord application, owned by whoever runs it. This page takes you from nothing to /judge answering in your server. Discord’s own walkthrough of the portal, Building your first Discord Bot, covers the same screens in general terms. The steps below say what to choose for this bot.

The bot is slash-command only. It needs no privileged gateway intents and no channel permissions, because it receives only its own commands and button presses and replies through the interaction. You never grant it the ability to read messages.

  • The first run done up to the point where the web page answers. The database is up and the data is loaded. Discord is the last thing to add, and nothing here spends money.
  • Manage Server in the Discord server you are adding the bot to. Adding an app to a server requires that permission (Installation Context).
  • A Discord account with email verified, which the developer portal requires.

Open the Developer Portal and choose New Application. The name is what members see beside /judge in the command picker and on the bot’s profile, so name it for your server (“Rules Judge”, “Club Judge”). The App Icon is the bot’s avatar. The project’s icon is a 512×512 PNG (assets/icon.png in the repository) if you want it, and any image works. Nothing else on the General Information page matters to the bot. The install link in step 3 carries the Application ID shown there.

Under Bot:

  1. Reset Token and copy the result into .env as DISCORD_TOKEN. Discord shows a token once. If you lose it, reset again. The token is a credential for the application, and every binary that reads it holds it as a redacted secret. Set JUDGE_OPERATOR_DISCORD in .env to your own Discord username at the same time. The bot does not start without it, and /help and /license show it.
  2. Leave all three Privileged Gateway Intents (Presence, Server Members, Message Content) off. The bot connects with no intents (Gateway Intents explains what they are). Turning one on changes nothing in the bot and, past 100 servers, would trigger Discord’s verification process for nothing.
  3. Public Bot can be off. It controls whether other people can use your install link. A bot you run for your own servers has no reason to allow that.

Under OAuth2 → URL Generator:

  1. Tick the scopes bot and applications.commands. bot puts a bot user in the server. applications.commands lets it register slash commands there (OAuth2 scopes).
  2. Under Bot Permissions, tick nothing. Replies, buttons and the “did you mean?” row all travel through the interaction, which needs no permission in the channel. The URL ends in permissions=0.
  3. Open the generated URL in your browser, pick the server, and Authorize. This is Discord’s bot authorization flow. The URL has the shape https://discord.com/oauth2/authorize?client_id=<application id>&scope=bot%20applications.commands&permissions=0. You can reuse it for every server you administer.

The newer Installation page in the portal (installation contexts and a Discord-provided install link) does the same job. If you use it instead, keep Guild Install as the only context, the same two scopes, and no permissions. The bot has no user-install behaviour.

The running bot registers its slash commands, per application. It registers them either in one server (instant) or globally (every server the bot is in, up to an hour to appear). See Registering a command. For a bot in one or two servers, register in the server:

  1. In Discord, User Settings → Advanced → Developer Mode on.
  2. Right-click the server icon → Copy Server ID (Discord’s help article Where can I find my User/Server/Message ID?).
  3. Put it in .env as GUILD_ID.

Leave GUILD_ID unset to register globally. Avoid switching back and forth. The bot registers the set it is configured for and does not remove the other, so members would see two identical /judge entries from the same bot until the stale set is cleared. The portal does not clear it. Overwriting a set with an empty list does (Bulk Overwrite Guild Application Commands). It needs the bot token, the Application ID from step 1 and, for a server’s set, the server id that GUILD_ID held before you blanked it:

Terminal window
DISCORD_TOKEN=... # as in .env
APPLICATION_ID=... # General Information → Application ID
GUILD_ID=... # the server the stale set was registered in
# clear the server's set (after moving to global registration)
curl -X PUT -H "Authorization: Bot $DISCORD_TOKEN" -H "Content-Type: application/json" -d '[]' \
"https://discord.com/api/v10/applications/$APPLICATION_ID/guilds/$GUILD_ID/commands"
# clear the global set (after moving to GUILD_ID)
curl -X PUT -H "Authorization: Bot $DISCORD_TOKEN" -H "Content-Type: application/json" -d '[]' \
"https://discord.com/api/v10/applications/$APPLICATION_ID/commands"

Clear only the set the bot is no longer configured for. The bot registers its own set again at every start.

Terminal window
docker compose up -d bot # or: cargo run --release -p judge-bot

The log line registered /judge, /card, /rule, /help, /license and /forget in one guild (or … globally) confirms registration. /help in the server confirms it end to end. Discord’s command picker shows your bot’s icon beside its commands, so another bot’s /judge in the same server does not conflict with yours.

Run this once:

Terminal window
docker compose run --rm refresh emoji # or: cargo run --release -p judge-ingest -- emoji

It uploads Scryfall’s mana and card symbols as application emoji. These belong to the application rather than to any server and need no emoji permission to use (Application-owned emoji). Without them answers render the literal {W}. The command is idempotent, reads DISCORD_TOKEN and needs no database.

The bot answers wherever its commands can be used, and that is Discord’s setting, not the bot’s. In Server Settings → Integrations, open your application and restrict /judge (or every command) to the channels, roles or members you choose (application command permissions). A rules-questions channel keeps follow-ups together, because history is per channel.

Two limits are the bot’s own: JUDGE_USER_LIMIT questions per member per JUDGE_USER_WINDOW_SECS (six per ten minutes by default, 0 turns it off), and JUDGE_CONCURRENCY runs in flight at once. Neither applies to /card and /rule.

An Incorrect rating tells the rater to contact you, and links the Wrong or unhelpful ruling issue form of the repository JUDGE_SOURCE_URL names when that is on GitHub. A fork has Issues off until you enable them under the repository’s Settings → General → Features.

Members holding a role whose name matches JUDGE_ROLE (default Judge) rate as judges. Their rating overrides the crowd’s smoothed average for that answer. Create the role in your server under Server Settings → Roles (Discord’s Role Management 101 covers it) and give it to the people whose rulings you trust. The name comparison is exact, and the role needs no permissions of its own.

One application can be in every server you administer. Reuse the install link from step 3 and register commands globally. The servers then share the process: one spend cap (JUDGE_MAX_USD), one concurrency limit and one judge role name. That is by design (D16). A community that wants its own budget runs its own instance, which is a second copy of the same compose file with a second application’s token.

Your application is bound by the Discord Developer Policy and Terms of Service like any other. Two points matter for a judgebot. It must not collect more data than it needs. This one stores questions per channel and ratings per user, and /forget deletes the latter. Magic content is used under Wizards of the Coast’s Fan Content Policy, which your instance’s /help already states.