Guide to Creating a Discord Bot in JavaScript with Discord.js v13

Introduction

Communicating online and staying in touch with people all over the globe has been a major promise from the dawn of the Internet - a web of communication and information. Fairly early on, applications sprung up that connected people all over the globe, allowing them to send messages, multimedia, perform live video and audio calls, allowing for international communication in a more interactive and engaging format than just phone calls.

The 2020 pandemic further increased the need to work remotely and communicate with friends and family online. Communication services, which were well-established by that point, saw a new wave of users and applications such as Zoom, Google Teams, Skype, Discord and Webex rose in popularity.

Discord is the application we'll be focusing on in this guide. As of 2021, it's one of the most popular communication platforms, used to facilitate communities of users in servers, the sharing of text, audio and video as well as live streaming to channels.

What sets Discord apart is how easily it can be adapted to certain themes and communities, with custom roles, authorization and channels, which allows users to interact with given servers in a unique way - more than just a group chat.

Note: At the heart of this customizability are Discord bots, which can be set up to react to messages, reactions or perform tasks periodically, allowing server administrators to give structure and create protocols of conduct.

Creating bots is fairly easy, and can be done in a wide variety of languages - such as C#, Java, Go, Lua, NodeJS, PHP, Python and Ruby, and in this guide, we'll take a look at how to create a Discord bot in JavaScript, as well as how to make the bot react to messages with its own responses, handle custom commands and how to change the bot's presence.

Discord bots are based on an Event-Driven Architecture, which is natural given the nature of the platform they're running on.

Before diving into the bot itself, if you're not familiar with the terminology used within Discord - we'll take a look at the basics.

Basics of Discord

What started as a gamers based application, nowadays became a general use application for communication. As previously mentioned, Discord is one of the most popular communication applications on the web.

As most applications of this type, it has options for text, audio and video communication and is also completely free. While one-on-one communication is available, a major appeal of Discord are its servers, which can serve as simple group chats, but can be expanded into being full-blown communities of thousands.

  • Discord servers

You can think of a Discord server as a big house consisting of multiple rooms. These rooms can be broken down into text and voice channels. If you would like to check out some of the most popular servers, you can use services that list servers and their invite links on websites such as Top.gg.

An individual enters a server through an invite link which can be permanent (anyone with it can join as long as the server is available), temporary (anyone can join in a given timeframe) or limited to certain individuals. Additionally, based on a user's authorization, they may or may not create invite links - and this is oftentimes automated through bots.

  • Discord channels

If the server is a house, one Discord channel is one room in that house. Channels are used to organize topics of discussion - such as a workplace having a Marketing channel, Development channel and a Memes channel.

Text channels allow users to send text, video files and images and you can ban multimedia and force text-only and vice-versa (you can delete text messages, and enforce only images) via bots.

Voice channels are only used for voice communication regardless of a user's presence in other text channels. A user can talk to other users in a voice channel and type messages in a separate text channel or stream a video within the voice channel.

Furthermore, a user may enter a channel only if their authorization allows it. By default, everyone can enter every channel, but through role-based authorization, you can hide channels from individuals with certain roles.

  • Discord bot

A Discord bot is a program written to automate certain tasks or moderate predictable behavior. For instance, you can use a bot to detect foul language and delete messages that contain it.

Discord bots are, ultimately, users.

Whatever a user can do, a bot can do - faster, and around the clock. Typically, bots are given superadmin authorization so they can kick, ban, create invites, delete messages, assign roles, etc. They're typically used to coordinate and maintain servers when user moderators and admins are not around, as well as to handle menial tasks.

Many public bots exist, which are open-source and work great as general purpose bots! Some of them even have APIs that you can fiddle around with, as well as web user interfaces that let you customize them to your liking so the average Joe can create a bot without any coding knowledge.

While these work well, it's a similar analogy to creating a website with services such as WordPress - you might want to build it from scratch yourself instead.

That being said, let's go ahead and create a Discord Bot user that we'll be automating.

Creating a Discord Bot User

Bots are ultimately users, but they have to be bots transparently. They're of a type User and people on the server know when a bot is a bot. To create a bot, and to avoid abuse and misuse - you have to register it through Discord's Developer portal:

discord.com/developers/applications

This page serves as a dashboard for all the applications you write:

When the page loads, in the top right corner you'll see a purple button - New Application.

You'll be prompted to enter a name for your bot. In this guide, we'll be calling it test-bot since it'll be a general simple bot, but you can get more creative! This bot will send a welcome message every time someone joins, which is a pretty typical use for a bot in a community. Additionally, it'll react to certain messages containing inappropriate language, react to people calling for it, as well as handle commands from users.

Let's enter a name for the bot:

When you click Create, you'll be taken to the application's settings. We'll be focusing on the Bot and OAuth2 tabs. The Rich Presence tab is used for integrating Discord into games (can be used by game devs to intertwine the features) and the App Testers tab is used to invite testers to test your app. This is a useful feature if your bot has superadmin powers, and you're skeptical about publishing it before testing it.

The first tab useful for us is the Bot tab. Here you can create a bot, change its profile picture, name and set a token.

Note: A token is practically a password for your bot. It is best if you do not reveal it to anybody, since it can be used to manipulate the bot in ways that can harm your Discord server.

Each application needs to be authorized and Discord uses OAuth2 for authorization - the industry-standard. Under the OAuth2 tab, we can select scopes for applications, and since we've created a bot, we'll be checking the box next to the bot scope. Right after checking it, another tab called Bot Permissions should pop up underneath the Scopes.

Here we will set all the permissions that the bot will have. Since we are making the bot that will mostly send text messages, this is the only part that is interesting for us:

Note: You might have noticed that the only General Permission we set is View Channels - because the bot needs permission to move from channel to channel in order to interact with the users.

As you select permissions, the OAuth2 URL generator will add your options with a unique client_id live. Once you select all of the options you wish to apply to the bot, copy and follow the link. Once you do - you'll be prompted to select a server to which to add the bot.

After selecting a server and verifying that you are not a robot, a bot should appear in the bot list. The bot will appear offline in the chat until it's coded, so no need to panic about its permissions quite yet, even if you haven't coded it.

Programming a Discord Bot in Node.js

With a Bot user set up, we can go ahead and actually code it!

Installing Discord.js

To code the bot, we'll be using Discord's SDK - Discord.js. It's available for download via NPM:

$ npm install discord.js

Once installed, we're ready to go! Let's create a folder for the project and initialize an empty Node project within it:

$ mkdir discord_bot
$ cd discord_bot
$ npm init

You can leave all the default options when instantiating the project, or set some of your own.

Important Note: In order to use the Discord.js module, you need to have Node 16.x+ installed on your system. Otherwise, a missing module 'node:events' error will be raised.

Understanding Bot Intents

Bot Intents are probably the most important concept to understand in order to manage your bot properly. Bot intents are a group of events that the bot will react to. They have their own FLAGS which can be set in order to precisely define which events we want our bot to react to.

If you don't define them properly, your bot will simply not react, even if you have the right code.

Intents are passed as an array when initializing the bot, which we'll see a bit later. For now, keep the syntax in mind:

Intents.FLAGS.INTENT_NAME

The macro INTENT_NAME is the only part we will be changing when wanting to add different intents. To let bot react to intents, go to the Bot tab at The Developer Dashboard. Under Privileged Gateway Intents, turn on Presence Intent and Server Members Intent.

Let's look into some of the intents defined in the Discord.js documentation:

  • GUILDS - reacts to any events containing a Create or Delete action - such as messageCreate.
  • GUILD_MEMBERS - reacts to events such as adding, updating and removing a new user to the server.
  • GUILD_MESSAGES - reacts to events such as Sending a message, Editing a message and Deleting a message.
  • GUILD_MESSAGE_REACTIONS - reacts to events such as Adding or removing a reaction to a message.

You may be wondering, what is a Guild all of a sudden? Just another name for a server. According to the Developer Portal:

Guilds in Discord represent an isolated collection of users and channels, and are often referred to as "servers" in the UI.

There's a decent list of intents, though, you'll generally be using only a few for most basic tasks.

Initializing a Discord Bot

The first action a bot could take is, well, being online. It's a passive action and can be scheduled to be online during certain times of the day, for instance. For now, let's just get the bot to be online on the server.

In the Bot tab of the dashboard, copy your bot's token - this token is used on the client-side to authenticate it. Then, in the index.js file of your Node project, let's import the Discord.js SDK, define the intents of the bot and set it to be online:

const {
    Client,
    Intents
} = require('discord.js');

const bot = new Client({
    intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MEMBERS, Intents.FLAGS.GUILD_MESSAGES]
});

bot.on('ready', () => {
    console.log(`Bot ${bot.user.tag} is logged in!`);
});

bot.login('YOUR_BOT_TOKEN'); // Replace the macro with your token

Here, we just initialize a Client object, which is the main interface for interacting with Discord's APIs. The Client is the bot. When initializing it, we pass in the array of intents.

Let's look at the other chunk of code:

bot.on('ready', () => {
  console.log(`Bot ${bot.user.tag} is logged in!`);
});

Again, Discord bots are based on an Event-Driven Architecture. The Client is an EventEmitter and we can listen to the emitted events and react to them.

If you'd like to read more about Event-Driven Architectures and handling events with Node - read our Guide to Handling Events in Node.js with EventEmitter!

In this case, we're listening to the ready event, and on() that event, we log that the bot is logged in. This requires prerequisite knowledge of when Discord sends events - for instance, it sends a ready event when a client logs in. The user object of the Client represents the Discord user that the bot really is.

The login() method, given the bot token, will log the bot in and emit this event, and the on() listener fires then, logging the message. Go ahead and run the script and observe the bot going online on the server:

$ node index.js
Bot [BOT-NAME] is logged in!

Awesome! It's online and logged in. Let's add another event listener to send welcome messages when users join.

Send Welcome Messages to a Channel

A classic task for a bot is to welcome users to a server. These can be as simple as "Welcome [user]!" to more elaborate welcomes, including randomized messages. The string you return is fully up to you and your imagination is the limit.

When a member is added to a discord server a guildMemberAdd event is fired, which you can listen to just like you'd listen to other events. In order to catch this event, we previously added an intent called GUILD_MEMBERS which allows us to listen to it.

First, let's capture the member that enters and print them to the console:

bot.on('guildMemberAdd', (member) => {
    console.log(member)
});

Run the script again, and when a new user enters, you'll be greeted with something along the lines of:

Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

GuildMember {
  guild: <ref *1> Guild {
    id: '<USER_ID>',
    name: undefined,
    icon: undefined,
    features: undefined,
    commands: GuildApplicationCommandManager {
      permissions: [ApplicationCommandPermissionsManager],
      guild: [Circular *1]
    },
    members: GuildMemberManager { guild: [Circular *1] },
    channels: GuildChannelManager { guild: [Circular *1] },
    bans: GuildBanManager { guild: [Circular *1] },
    roles: RoleManager { guild: [Circular *1] },
    presences: PresenceManager {},
    voiceStates: VoiceStateManager { guild: [Circular *1] },
    stageInstances: StageInstanceManager { guild: [Circular *1] },
    invites: GuildInviteManager { guild: [Circular *1] },
    deleted: false,
    available: false,
    shardId: 0,
    memberCount: NaN
  },
  joinedTimestamp: 1633939430866,
  premiumSinceTimestamp: null,
  deleted: false,
  nickname: null,
  pending: false,
  _roles: [],
  user: User {
    id: '<USER_ID>',
    bot: false,
    system: false,
    flags: UserFlags { bitfield: 0 },
    username: '<MEMBER_USERNAME>',
    discriminator: '<MEMBER_DISCRIMINATOR>',
    avatar: null,
    banner: undefined,
    accentColor: undefined
  },
  avatar: null
}

There's a bunch of information on the GuildMember (Discord user within a server) that just joined, though, the interesting bits for us are:

  • guild - server information, with properties such as id, name, members, channels, presences, etc.
  • _roles - an array containing the user's roles in the server, if any exist.
  • user - user information, with properties such as id, bot, username, discriminator, etc.
  • discriminator is an integer which is a part of the full username, which usually looks like: randomUser#1234, so 1234 is a discriminator.

There's a lot of confusion as to what's the difference between a User and a GuildMember, and it really just boils down to:

A User is a global Discord user, and a GuildMember is that user within a server. It's a representation of a user, with permissions, roles, nicknames, etc. Each User can have multiple GuildMembers bound to it, with a GuildMember for each server. You can essentially see it as a "face" of a user in a given context.

Now, let's tweak the code so that it doesn't print the GuildMember instance, but rather greet them. Typically, there's a dedicated text channel for greetings and welcomes - and you don't want to use the #general channel for this.

Note: Additionally, there's also oftentimes a #bot-spam channel for menial bot output that might clutter actual chat channels. You can, of course, simply not send certain messages, but it helps to log them either on the server or an external file for later use.
You can hide this channel from regular users if you don't want it to be public.

You can create a new channel by clicking on the dropdown menu next to the server's name and clicking on Create Channel. The prompt will ask you whether you want a Text or Voice channel. We'll select a text one, and name it #welcome.

We can send a message to a channel by obtaining its instance and the send() method. Instances are obtained through a channel's ID, which is known as a constant during your bot's lifetime.

To fetch a channel's ID, we must turn the Developer Mode on, and use the UI of the application. Head over to your Discord settings, by clicking the little gear wheel at the bottom left of the application. Under App Settings in the left part of the window, you'll find an Advanced tab. There, turn on the Developer Mode. This will allow us to retrieve the Channel ID. This is done by right-clicking on the channel's name and clicking Copy ID.

Let's use that ID to fetch() a channel and send a message to it, on() the event of a user joining in:

bot.on('guildMemberAdd', (member) => {
    const channelId = 'CHANNEL_ID'; // The Channel ID you just copied
    const welcomeMessage = `Hey <@${member.id}>! Welcome to my server!`;
    member.guild.channels.fetch(channelId).then(channel => {
        channel.send(welcomeMessage)
    });
});

As we've seen previously, a guild has information about its channels, which we can retrieve with member.guild.channels. Finally, in order to get the channel that we want, we call the method fetch() and pass the channel ID as a parameter. This method returns a Promise, so it must be followed with a then(), where we define a function to be executed after we retrieve the desired channel. In this case, this will be a simple .send() containing a text of the message.

Again, you can do much more here, such as pick a random message from a list of templates, log the information outside of the channel, etc.

If you'd like to read more about Promises, read our Guide to Promises in Node.js!

The <@${member.id}> message simply tags the user in a message, so they receive a notification for this message when joining the server. You can omit the @ in the beginning to skip the tagging part, though, servers oftentimes tag people to get their attention and redirect them to read a server's rules, for instance.

When a user joins your server, they'll be greeted with your message:

Awesome! Let's go ahead and give our bot a bit more autonomy, by allowing it to pick up certain phrases or words and reacting to them.

Detect and React to Messages in the Channel

Once people join your server, they'll be sending messages. Each message that's sent creates a messageCreate event, which you can, unsurprisingly, listen to. You don't want to spam users on every message, but there are cases where your bot might want to react to something.

For instance, if someone's calling the bot or a user includes insensitive words, you might want to have the bot respond or take action. For instance, let's add two listeners - one that gets the bot to react to users calling for it and one that reacts to when people send inappropriate words within their messages.

Note: To detect messages, you need the GUILDS and GUILD_MESSAGES intents.

When catching a message, the message object contains the content of the message, channel from which the message came, etc. Then, it's just a matter of checking whether that string contains other strings:

bot.on('messageCreate', (message) => {
    if (message.content.toLowerCase().includes('hey bot') || message.content.toLowerCase().includes('general kenobi')) {
        message.channel.send('Hello there!');
    }
});

bot.on('messageCreate', (message) => {
    if (message.content.toLowerCase().includes('fudge') || message.content.toLowerCase().includes('pudding')) {
        message.channel.send('Such language is prohibited!');
    }
});

The bot will only respond to messages defined in the if clause and will respond in the same channel that the message originated from, as obtained from the message object itself. You can, of course, send the message to a different channel as well or direct it to a hidden channel reserved only for admins.

Once you run the script again, and type in a few messages, the bot will talk back!

Note: The bot will react to its own messages, as they also fire the messageCreate event when sent.

If your response to a user contains words that might match other if statements, your bot may end up replying to itself ad infinitum:

bot.on('messageCreate', (message) => {
    if (message.content.toLowerCase().includes('fudge') && message.content.toLowerCase().includes('pudding')) {
            message.channel.send('Why of course, I love fudgy pudding cake!');
    } else if (message.content.toLowerCase().includes('fug') || message.content.toLowerCase().includes('pudding')) {
        message.channel.send('Such language is prohibited!');
    }
});

Fudgy pudding cake is delicious, so if someone tells us to fudge the pudding, they must be referring to the process of creating the cake, so we respond with a nice message back - however, the "pudding" in the response is matched to a new event handler created by the response. Thus, the bot responds to that message with its own:

Yikes. You can easily create an infinite loop like this. For instance:

bot.on('messageCreate', (message) => {
    if (message.content.toLowerCase().includes('a')) {
            message.channel.send('a');
    }
});

This piece of code will respond with "a" whenever a message contains "a" - which results in an infinite stream of 5-message bursts of the bot saying "a":

Be careful when checking and responding to messages, and be careful of how powerful methods such as includes() or contains() can be. Thankfully, avoiding this issue is pretty simple.

Stop Bot From Replying to Itself

You don't want the bot to reply to itself, or contradict itself in examples such as the one with fudgy pudding cake. Thankfully, you can easily check who sent the message and ignore it if it's sent by a bot, totally avoiding the infinite loop issue:

if (message.author.bot) {
    return;
}

Let's add this check in our earlier example:

bot.on('messageCreate', (message) => {
    if (message.author.bot) {
        return;
    } else if (message.content.toLowerCase().includes('fudge') && message.content.toLowerCase().includes('pudding')) {
            message.channel.send('Why of course, I love fudgy pudding cake!');
    } else if (message.content.toLowerCase().includes('fug') || message.content.toLowerCase().includes('pudding')) {
        message.channel.send('Such language is prohibited!');
    }
});

The first if statement checks whether a bot sent the message or not. If it did, the rules below don't really apply, so we never enter the infinite state. Let's send another heart-felt message to the bot, regarding their cake:

Works great!

Creating Bot Commands

Since we've seen how to process the messages sent to the server, let's now define some commands for it to allow users to command it through a controlled environment. First of all, let's welcome our users with a different message. We have to let them know how to find the list of commands, and how to use them:

bot.on('guildMemberAdd', (member) => {
    const channelId = 'CHANNEL_ID';
    const welcomeMessage = `Hey <@${member.id}>! Welcome to my server! \n See commands list by typing: $listCommands`;
    member.guild.channels.fetch(channelId).then(channel => {
        channel.send(welcomeMessage)
    });
});

Commands are, really, just messages that follow a certain form you've set for yourself, and we can check for commands just as we've checked for messages so far.

Note: You don't have to use the $ symbol to denote commands. Some use dots (.), slashes (/), etc. Generally, some symbol is used to denote a command rather than a message.

Let's code the commands, including the $listCommands one. We can do this inside one of the existing messageCreate listeners or a new one - we'll just process different message.content. To stylize the response of the command list, we'll use a MessageEmbed which looks similar to a quote block. It allows us to set a color, title, description as well as fields with their keys (names) and values to describe commands.

To use MessageEmbed instances, we need to import it from Discord.js first so let's update the initially imported classes:

const {
    Client,
    Intents,
    MessageEmbed
} = require('discord.js');

Now, we can create a new if statement to handle the commands:

bot.on('messageCreate', (message) => {
    if (message.content.toLowerCase().includes('hey bot') || message.content.toLowerCase().includes('general kenobi')) {
        message.channel.send('Hello there!');
    }
    
    if (message.content == '$listCommands') {
        const exampleEmbed = new MessageEmbed()
            .setColor('#ffd046')
            .setTitle('Server Commands')
            .setDescription('Here you can see the list of the commands used on the server: ')
            .addFields(
                { name: "`$like`", value: 'Likes the current message' },
                { name: "`$dislike`", value: 'Dislikes the current message'},
                { name: "`$random`", value: 'Returns a random number'},
            )
        message.channel.send({embeds: [exampleEmbed]})
    }

    if (message.content == '$like') {
        message.react('👍');
    }

    if (message.content == '$dislike') {
        message.react('👎');
    }

    if (message.content == '$random') {
        message.react('✅');
        let randomNumber = getRandomNumber(0, 1000);
        message.reply(`Your random number is ${randomNumber}.`)
    }
});

We also have an auxiliary method to generate a random number within a certain range:

function getRandomNumber(min, max) {
    return Math.floor(Math.random() * (max-min) + min);
}

The if-else clause may not be the best option for processing commands, but if there are a few of them, it's just fine. However, if you wish for your server to have a wide range of commands, you should probably consider putting them in a file and have a bot find a proper command and reaction to it when a message is sent instead.

Running the script again allows us to use these commands:

Changing a Bot's Presence

When a bot is logged in, you can change its presence. This could be to signify that the bot is on standby, waiting for commands or tasks, or simply to set the tone of your service. For instance, you can set it to "play a game" while being online or "serve the community".

It's an optional little gig, but can go a long way in terms of humor and denoting when and whether the bot is available and online:

bot.login('YOUR_BOT_TOKEN').then(() => {
    bot.user.setPresence({ activities: [{ name: 'a random game', type: 'PLAYING' }], status: 'online' });
});

Presence is defined by activities and a status. Activities are an array of activities a user is doing. They have a name and a type. The type is a macro and can be set to PLAYING, WATCHING, LISTENING, STREAMING and COMPETING. They resemble the start of the sentence for the presence, so the name is its continuation.

A presence for the code above should look like this:

Conclusion

Discord bots are malleable and your imagination is the limit when it comes to coding them. They are undeniably one of the major reasons Discord broke out as a community-building platform and an excellent messenger and communication application.

Discord has a really large community and a nice tradition of Discord servers, with a lot of really useful ones. If you have an idea of your own, now is the time to start developing a server!

Last Updated: March 27th, 2023
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms