Introduction

Welcome to this user friendly guide for coding your very first Discord bot. Following this guide you will first complete a few steps to get started and then start writing your own code.
This guide was made to make starting out coding Discord Bots less stressful and more at ease for everyone. Feel free to come back to it if you have any doubts or ask for help in our Discord server.

Requirements

This guide follows the process of creating a Discord bot using Python, so make sure you have it installed.
To check, open the Command Prompt and type this command: python --version.

It should display something like this:

python --version

If it doesn't, try reinstalling it or following an external guide.
There are more requirements, but those will come up later on depending on a few choices we make, so for now let's keep going.

Choosing a library

The first thing we have to look at is what library to build our bot on. We have a few choices based on what type of bot you want yours to be.

The first option is classic commands, invoked by a special character of your choice, normally !. For example, !echo is a classic command.
However, classic commands are getting replaced rapidly by our next choice, which is slash or application commands.
These are commands called by pressing slash / and then the command name and they popup on the left of your screen. These are the preferred choice as they can have a description, visibly list any command arguments and more.

For creating slash commands, we can use discord.py, which is the library also used for classic commands, but it's more complex and advanced. It can be better configurable, but for beginners I suggest a fork of discord.py named py-cord.

Getting Started

Let's jump in and start creating. You can code Discord bots directly on note pad if you want, but I highly suggest using an IDE or a code editor, I personally use VS Code.
Assuming you're using VS Code, download a Python extension and create a new file named mydiscordbot.py. .py is the extension used by Python files.
So now you have a blank python file. The first thing you want to do is to import the Discord library. So, in the first line type this: import discord. Now, we have Discord in our script and we can proceed to import the next requirements required to make slash commands.
In the second line, we can now type: from discord.ext import commands, tasks. This is called the imports section, and you can come back here and import all the libraries you need for your project.

Now we have to start setting up the Discord bot. This is made using intents, you can think of them like the intention of the bot, what finality it has. There are various types of intents, but it's easier to just include them all, so shove this in there below the imports section: intents = discord.Intents.all()
Now we can finish the setup by adding a line that tells the bot basically who he is and what he has to do. bot = commands.Bot(command_prefix='!', intents=intents). An important thing to note is that command_prefix is not used for slash commands. In fact, py-cord allows to create classic commands too that follow this prefix. The prefix is the special character we saw earlier when choosing a library.

Your script should now look like this:


import discord
from discord.ext import commands, tasks

intents = discord.Intents.all()

bot = commands.Bot(command_prefix='!', intents=intents)
Now we setup our variables. Since we don't have any custom yet, there is only one we have to setup. That is the Bot Token. It is strongly recommended to not hard code it in the script and use libraries like dotenv to secure your secrets, but as long as you don't share this script anywhere it should be fine. If your bot grows, you might want to consider hiding these vulnerabilities. Remember that anyone with your Bot Token can make the bot do anything at his liking.
Anyway, let's add our token. You can name it whatever you want, but it's nice and organized to name variables after their use and not random letters.

Good name: bot_token Bad name: bXqLpAzMnF

Add this in your script: bot_token = "Temporary text here. We'll learn how to use the token at the end!".

Coding your first command

Let's make the first command. We're gonna start with a simple /hello command that responds Hello, and the user's name. To do this, we first need to define the command. To do this, let's write:


@bot.slash_command(
  name="hello", # Change this name to any of your liking! This is the command name that will appear after the slash on Discord.
  description="Say hello!" # Supports Capital Letters
)

Before we continue with the command, it's important to note that slash commands may take up to an hour to sync and appear on your Discord server. So, there is a little trick we can use.
If you plan on making your bot on a restricted server list (Not a public bot), we can make a list of guilds (servers) where the commands appear on. This makes the process of syncing significantly faster. To do this, in the variables section we add a list called guild_ids with values all Guild IDs where the commands need to appear. For example:
guild_ids = [1130229274452971581, 748579207420051515]
Then, in the command decorator, we add a comma after the description and we add this parameter: guild_ids=guild_ids. It should look like this:

@bot.slash_command(
  name="hello", # Change this name to any of your liking! This is the command name that will appear after the slash on Discord.
  description="Say hello!", # Supports Capital Letters
  guild_ids=guild_ids
)

This way the commands sync almost immidiately. Now we can proceed making the actual command code:

async def hello(ctx):
    await ctx.respond(f"Hello, {ctx.author.mention}!")

As you can see, we define a function called hello that responds to the command (ctx) with Hello, {ctx.author.mention}!. Since ctx stands for command, by logic ctx.author is the user who executed the command, and we do mention that mentions the user in the message.
This is the full command:

@bot.slash_command(
  name="hello", # Change this name to any of your liking! This is the command name that will appear after the slash on Discord.
  description="Say hello!", # Supports Capital Letters
  guild_ids=guild_ids
)
async def hello(ctx):
    await ctx.respond(f"Hello, {ctx.author.mention}!")

We can also decide to make the response to the command only visible to the command executor (we'll call them command author from now on) by passing ephemeral=True in the response code, like this: await ctx.respond(f"Hello, {ctx.author.mention}!", ephemeral=True)

More command types

Now let's make a command to give a role to a member. This command should only be available to authorized members and you might not want it clearly visible to everyone. This is when classic commands come back into play, because by using the prefix, there isn't written anywhere what commands are available, so it is hidden.

For now let's focus on making a list of individuals able to operate with these risky commands. The more logic choice is to make a role in your Discord server and let that role use those commands, but since we want to get some grip on python, let's use a less logical method but that can still be useful to us.
Let's go and create a new list just under the guild_ids list called STAFF_ID. Here, just like the guilds, add the User ID of all users authorized to use these commands. It should look something like this:
STAFF_ID = [984121073173921863, 995040689576497162]
Now we can start making a classic command. This is easier than a slash command, so follow through:


@bot.command()
async def role(ctx, member: discord.Member *, role_name):

What did we do here? Well, classic commands don't have a complex decorator like slash commands, so we just use @bot.command since they get the name from the function below them. In the function, we added the arguments member, * and role_name. What are these?
member is gonna be what user in the server we are assigning the role to. We're using discord.Member because this way we can ping (@) a user on our server instead of having to copy his username or User ID. * is a symbol that makes everything typed after the previus parameter part of the next one. To make things clearer, normally you can't have spaces in classic commands, because if you do, the space means you skipped to the next parameter. For example, how do you give the role "Cool" to John Smith, when if you type "John Smith", "Smith" goes to the parameter "role"? Well, on the code you add *. Now, everything typed after the member parameter will count as role, so you can have roles with spaces in their name and the command will still work.

After this very boring explanation we can finally begin with this code.
role = discord.utils.get(ctx.guild.roles, name=role_name) We get the role by passing the role name

if role: # If the role is found
        await member.add_roles(role) # We add the role to the member
        embed = discord.Embed(
            title="✅ Success",
            description=f"Role {role_name} has been added to {member.name}.",
            color=discord.Color.green()
        )
        await ctx.send(embed=embed)
        print(f"Role {role_name} added to {member.name} by {ctx.author.name}.")
    else:
        embed = discord.Embed(
            title="❌ Error",
            description=f"Role {role_name} not found.",
            color=discord.Color.red()
        )
        await ctx.send(embed=embed)
        print(f"Role {role_name} not found for {member.name} by {ctx.author.name}.")

Don't forget the proper indentation:

@bot.command()
async def role(ctx, member: discord.Member *, role_name):

    role = discord.utils.get(ctx.guild.roles, name=role_name)
    
    if role: # If the role is found
        await member.add_roles(role) # We add the role to the member
        embed = discord.Embed(
            title="✅ Success",
            description=f"Role {role_name} has been added to {member.name}.",
            color=discord.Color.green()
        )
        await ctx.send(embed=embed)
        print(f"Role {role_name} added to {member.name} by {ctx.author.name}.")
    else:
        embed = discord.Embed(
            title="❌ Error",
            description=f"Role {role_name} not found.",
            color=discord.Color.red()
        )
        await ctx.send(embed=embed)
        print(f"Role {role_name} not found for {member.name} by {ctx.author.name}.")

There we go. Now the command will available by typing the prefix you setup at the start plus the command name. In my case, !role @Fox Owner

Coding your first event

Now we can add an event that will tell us, once the bot is launched, when it is operational. While it just takes a couple seconds for it to be operational once launched, it's nice to see a confirmation that the bot has successfully launched up.

boot-up with event

To implement it, we can use this code:


@bot.event
async def on_ready():
    print(f'Logged in as {bot.user}')
                    

A nice touch is that inside the on_ready() function we can add more functions that will run on bot startup. For example, you could create a function called say_hello() that sends Hello! I've just booted up! to a channel and add it after the print statement like this:

print(f'Logged in as {bot.user}')
await say_hello()

await is used when sending a message and more, since it goes down a more complex path, if the function you're making uses any tool of the Discord API like getting a channel or sending a message, it will probably require await before calling the function. Don't worry if you forget, the console will let you know you need it in your code.

There are many types of events in the Discord API, so I suggest looking them up on the documentation if you're curious. In this tutorial we'll be using just a few, but that doesn't mean it's all there is.