Adam A
6 min readSep 18, 2022

Learn how to integrate the Discord API into your applications — this is perfect for NFT applications but applies to all others! Step by step, incl design.

Hi! I’m Adam — this is my first blog post on Medium, and I’m excited to show you what I’ve been working on!

Today I’ll be going over a recent feature I’ve implemented for an NFT startup.

The issue: we needed a method of providing exclusivity to some NFTs and incentives to increase our Discord server’s user headcount.

Let’s go over some things:

  • A Guild is a Discord server
  • A Channel (Voice or Text) exists within a Guild.
  • A Discord user can be assigned a Role to allow them access to channels, administrative tasks, etc.

Here’s what I knew:

  • The app is an NFT store where you can buy and collect trading cards for a particular sport. What does this mean? Rarity. We have tiers of rarities, which provides exclusivity — something I can leverage.
  • The Discord API can assist you with admin tasks. These include (but are not limited to) adding users to Guilds and assigning users roles.

How I put all of this together:

We can create NFT exclusivity AND acquire more users in our Discord by allowing users access to secret channels based on their NFT trading card purchases.

On the functional side, we have a few things:

1 Admins need to be able to attach a Discord role to an NFT before they create it. This will require using the Discord GET Roles API, which retrieves all roles for a Guild ID. Careful: this does retrieve all roles — including admin roles. I show how I accounted for this below.

2 Users need the ability to link their Discord account to our application (so we can retrieve their Discord IDs and permission to add them to a Guild). I used Discord Oauth for this.

3 If a user purchases an NFT trading card, they should be assigned (on Discord, in our guild) a unique role that is attached to that trading card. This will unlock a secret channel for them. There are two cases here.

Case 3.1: If the user has already linked their Discord, call the Add Guild Member Role API, which assigns them a specific role.

Case 3.2: If the user hasn’t linked their Discord (Or even joined our server), that’s fine. The next time they connect their Discord through our app via OAuth, they should be assigned all roles from all purchases (for this walk-through, assume purchases/NFT relations are already handled).

The system design:

Note: I used discord.js lib for this, but you don’t have to — these are just standard REST calls.

Let’s start with (1) — Let’s assume we already have an admin control panel for NFT creation, an ETH provider to deploy the contracts, and some DB to persist NFT metadata.

We need to retrieve our Discord Guild’s roles to list them on the UI side and attach them to an NFT as required.

Here’s what I came up with for a system design:

You’ll notice that to retrieve the list of roles that live within our guild, the admin control panel client calls our internal server, which calls the Discord GET Roles API.

Sure, you can make this API call directly from the client, but this exposes your auth token to the client, and bad actors can take advantage of that.

By the way, here’s a tutorial on how to acquire a Discord Bot Token

Here’s what the service function looks like:

import { REST, Role } from 'discord.js';const BOT_TOKEN = 'YOUR_DISCORD_BOT_TOKEN'
const blacklistedRoleIds = new Array('Add', 'some', 'blacklisted', 'role', 'ids', 'here', 'so', 'you', 'don't', 'accidentally', 'expose', 'them','to','users')
const DISCORD_GUILD_ID = 'YOUR_GUILD_ID'
export const findDiscordRoles = async (): Promise<Role[]> => {
try {
const rest = new REST().setToken([YOUR DISCORD BOT TOKEN HERE]);
const roles = (await rest.get(`/guilds/${DISCORD_GUILD_ID}/roles`)) as Role[];
return roles.filter((role) => !blacklistedRoleIds.has(role.id));
} catch (error) {
logger.error(error);
}
return [];
};

Okay, now we have a way of retrieving roles.

Let’s move on to (2) the ability to connect your Discord to our app via Discord OAuth. We can also knock out (3.2) here while we’re at it.

For this, we begin by redirecting the user to:

https://discord.com/api/oauth2/authorize?client_id=[YOUR_DISCORD_CLIENT_ID]&redirect_uri=[YOUR_DESIRED_REDIRECT_URI]&response_type=code&scope=identify%20guilds.join

This does a few things. It will open up the Discord Login page for the user to enter their credentials and, upon authorization success, will redirect the user to the redirect_uri given. We are asking for scopes identify and guilds.join because we want the ability to identify the user and the ability to add them to our guild. Think of scope like permissions on an Android/iOS device.

Once the user logs in, a code will be appended to the redirect_uri link as such: www.myredirecturilink.com/account&code=[some_random_code_here]

We then send this code over to our backend server (in this case, through a PUT endpoint named /link-discord) — Within our server, using this code, we will ask for an access_token for this user using https://discord.com/api/oauth2/token

After retrieving their access_tokenWe can use that to make an identify call to https://discord.com/api/v10/users/@me which returns that user's ID plus some other info.

Now that we have the discordId let’s persist it (link it) to our app’s user. At the same time, let’s grab their NFT purchases and save the ones that have a discordRoleId attached (from (1))

Now let’s attempt to add the user to the guild using the Add Guild Member API (in case they're not already in there), and then let's assign them all the roles from their purchases using the Assign Guild Member Role API

import { REST } from 'discord.js';export const giveDiscordUserRole = async (roleId: string, discordUserId: string): Promise<void> => {
try {
const rest = new REST().setToken([YOUR DISCORD BOT TOKEN HERE]);
await rest.put(`/guilds/${[YOUR DISCORD GUILD ID]}/members/${discordUserId}/roles/${roleId}`);
} catch (error) {
logger.error('giveDiscordUserRole rest error', e2json(error));
}
};
export const addDiscordUserToGuild = async (
discordUserId: string,
accessToken: string
): Promise<void> => {
try {
const rest = new REST().setToken([YOUR DISCORD BOT TOKEN HERE]);
await rest.put(`/guilds/${[YOUR DISCORD GUILD ID]}/members/${discordUserId}`, {
headers: {
Authorization: `Bot ${[YOUR DISCORD BOT TOKEN HERE]}`,
'Content-Type': 'application/json',
},
body: { access_token: accessToken, roles: [YOUR VERIFIED ROLE ID HERE] },
});
} catch (error) {
logger.error(error);
}
};

Here’s what the final system design looks like:

And that’s it for (2) and (3.2)!

Now, onto the final case — (3.1)

This one is simple. The user buys an NFT on the client-side — the client calls some /nft/purchase endpoint — the contract is deployed to the chain, maybe also goes through some payment processor, and if the purchase is successful, then let's check for the following:

(A) Does the user have a discordId attached? (have they linked their Discord?)

(B) Does the NFT have a discordRoleId attached?

If yes to both, then let’s call the Assign Guild Member Role API

As such:

import { REST } from 'discord.js';export const giveDiscordUserRole = async (roleId: string, discordUserId: string): Promise<void> => {
try {
const rest = new REST().setToken([YOUR DISCORD BOT TOKEN HERE]);
await rest.put(`/guilds/${[YOUR DISCORD GUILD ID]}/members/${discordUserId}/roles/${roleId}`);
} catch (error) {
logger.error('giveDiscordUserRole rest error', e2json(error));
}
};

If no to (A), that’s fine — next time that they link their Discord, we will run through scenario (3.2)

If no to (B), we do nothing!

And that’s it! I hope you enjoyed this quick (and very high-level) run-through of a Discord integration. Please feel free to ask any questions and rip this apart!