How to Create a Bot on Discord a Developer's Guide

August 1, 2026

How to Create a Bot on Discord a Developer's Guide

STOP!

Want an easy way to post on social media with an API?

Just use our unified social media API. One reliable endpoint for social media and 9 more platforms. Integrate in minutes and cut development time by 90%.

  • We manage auth, rate limits, and breaking API changes
  • Automatic retries and durable job queues
  • Fully white-labeled. Your audience never sees Mallary
  • Officially verified and approved to post on all platforms
Learn more
fetch('https://mallary.ai/api/v1/post', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    platforms: ["youtube", "facebook", "instagram"],
    message: "Check out our new product!",
    media: [{ url: "https://files.mallary.ai/launch-video.mp4" }],
    comments_under_post: ["comment 1", "comment 2", "comment 3"],
    auto_reply_enabled: true,
  })
})

You're probably here because the bot looked easy at first. You created the app, pasted a token, maybe got a hello-world reply, and then it stopped responding the moment you changed a permission, restarted the process, or tried to move it off your laptop. That's the point where how to create a bot on Discord stops being a tutorial exercise and becomes a real backend system with identity, event streams, and operational hygiene.

Discord's own bot flow is built around three non-optional steps, create an application, add a bot user, and invite it with an OAuth2 URL that uses the bot scope and only the permissions the bot needs. Discord also splits bot behavior across the Gateway for real-time events and the HTTP API for REST actions, which is the right mental model for everything that follows, from command handling to reliability work (Discord bot overview). If you treat the bot like a long-lived service instead of a snippet, the rest of the build makes sense much faster.

Table of Contents

What Building a Discord Bot Actually Involves

An infographic illustrating the eight essential steps for creating, deploying, and maintaining a functional Discord bot.

A Discord bot is not a page, a webhook, or a one-off script. It's a long-lived network process that keeps a connection open, listens for events, and makes API calls when it needs to act. Discord's docs describe that split clearly, the bot can connect through the Gateway for live events, the HTTP API for REST actions, or both, and that's the technical base for almost every bot people run (Discord bot overview).

From registration to operation

The build has four real phases. First, you register the bot in the Discord Developer Portal. Second, you write code that authenticates with the token and responds to events. Third, you deploy it to a machine that stays on. Fourth, you operate it, which means watching logs, rotating secrets, and fixing permission mistakes when they show up.

That fourth part is where most beginner guides disappear. They stop once the bot replies in a test server, but the moment the token leaks, an intent is missing, or the host sleeps, the bot turns into a ghost and nobody knows why. Discord's standard invite-and-permissions flow is meant to keep setup controlled, while real bot projects still reveal the extra engineering needed for custom behavior, especially around tokens, intents, and permissions (stat-bot README).

Practical rule: if the bot needs a server role, a channel mention, or message text, decide that before you write code. The install-time permissions and gateway configuration shape the debugging surface later.

A useful mindset shift is to think in terms of register, code, deploy, operate. That's also why a public launch listing like the Marauder Bot launch listing can be helpful to inspect, not because it's a template to copy, but because it shows how a production-facing bot is presented once the quick start is over.

Registering Your Application and Bot User

The portal work is simple, but the small choices matter. Start by creating a new application in the Discord Developer Portal, give it a name that won't embarrass you later, and set an icon if you're shipping anything beyond a throwaway prototype. Then open the Bot tab and add a bot user to that application, because the application itself is not the identity that connects to Discord.

Set up the app the right way

Once the bot user exists, copy the token immediately into a password manager or another protected secret store. Discord's quick-start flow treats the token as the credential your code uses to authenticate, while the invite link is what installs the bot into a server (Discord quick start). The token is not a project setting, it's a password.

The next decision is the OAuth2 invite URL. Use the bot scope, then grant only the permissions the bot needs. For a plain chat bot, that usually means things like sending messages, reading history, and embedding links. If you're building a private admin tool, you might choose broader permissions, but that should be a deliberate choice, not a lazy default.

Avoid the “just give it Administrator” habit unless the bot is for a tightly controlled private server. Over-granting permissions makes later debugging harder, and it widens the damage if the token ever leaks.

A good sanity check is to invite the bot into a test server before you write any logic. It should appear offline until your code connects, and that's fine. If it never shows up, the problem is usually portal configuration or a bad invite URL, not your command handler.

One reason this flow feels cleaner today is that modern bots often advertise one-command setup, while older project readmes demanded cloning code, setting tokens, enabling intents, wiring a database, and generating invite links by hand. If you want a feel for the modern end of that spectrum, a setup like start with RenderIO auth shows how stripped-down onboarding is becoming in developer tooling.

Intents and Permissions Without the Confusion

Many bots break due to OAuth2 permissions and gateway intents. People mix them up, then debug the wrong layer for hours. Permissions decide what the bot can do inside a server after it's installed. Intents decide what events Discord will stream to the bot in the first place.

An infographic explaining the difference between Android intents and permissions, detailing their purposes and key types.

Separate server permissions from gateway intents

Think of permissions as server-side authorization and intents as event subscription. If the bot can send messages but can't read them, it'll feel broken even though the invite looked correct. Discord's documentation and common tutorial setups both emphasize that bot behavior depends on the intents and permissions configured at creation time, so you can't treat those screens as decorative settings (Discord quick start).

The most common trap is MESSAGE CONTENT INTENT. When a bot needs to read message text, that toggle has to be enabled in the Developer Portal, and the bot has to reconnect before the new gateway settings matter. If the bot replies to slash commands but not to plain text, that's often the first place to look. It's usually not a logic bug.

Another place people drift is privileged intents. These are not magic, but they do require explicit configuration in the portal, and in some cases they have to be reflected in the invite flow too. A bot that only needs slash commands, pings, or embeds should stay lean. A bot that parses user messages needs more, and that extra access should be justified.

If a command stopped working after a portal change, reconnect the process before you touch code. Intents are negotiated at the gateway level, so a stale session can keep old behavior alive.

A fast way to reason about the setup is to ask two questions. What can the bot do once it's inside a server? What events does it need to receive to do that job? If the answer to the second question is “message text,” then MESSAGE CONTENT INTENT belongs in the checklist.

Writing the Bot in discord.js and discord.py

The usual choices are discord.js in Node and discord.py in Python. Both are solid, and both reduce the painful part of bot work, the event loop, command routing, and response handling. The small examples below are intentional, because the job is not writing clever code. It is setting up a bot that keeps behaving once it is deployed, restarted, and asked to handle more than a toy command.

A /ping command is the cleanest place to start. It confirms that the token is valid, the bot can connect, the command is registered, and the runtime stays up long enough to answer. If that baseline fails, the problem is usually in setup, not in your command logic.

A minimal event loop and slash command

For either library, start the same way. Create a project directory, install dependencies with your package manager of choice, load the token from an environment variable, and run the process in a way that survives restarts. Discord's documentation covers the basic project setup and bot flow in the Discord quick start, and the bot overview is useful for understanding how the application and bot user fit together.

For discord.js, a stripped-down version looks like this:

import 'dotenv/config';
import { Client, GatewayIntentBits } from 'discord.js';

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

client.once('ready', () => {
  console.log(`Logged in as ${client.user.tag}`);
});

client.on('interactionCreate', async interaction => {
  if (!interaction.isChatInputCommand()) return;

  if (interaction.commandName === 'ping') {
    await interaction.reply('pong');
  }
});

client.login(process.env.DISCORD_TOKEN);

For discord.py, the equivalent setup is just as direct:

import os
import discord
from discord import app_commands
from discord.ext import commands

class MyBot(commands.Bot):
    async def setup_hook(self):
        guild = discord.Object(id=YOUR_TEST_GUILD_ID)
        self.tree.copy_global_to(guild=guild)
        await self.tree.sync(guild=guild)

intents = discord.Intents.default()
bot = MyBot(command_prefix='!', intents=intents)

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

@bot.tree.command(name='ping')
async def ping(interaction: discord.Interaction):
    await interaction.response.send_message('pong')

bot.run(os.environ['DISCORD_TOKEN'])

Development is faster if you register slash commands against a test guild first. Global commands are fine once the bot is stable, but propagation delays make them annoying while you are still changing handlers and names. Guild-scoped commands appear quickly, which saves time when you are checking whether registration, permissions, and the runtime all line up. If a handler needs more time than a simple reply, defer the interaction early so the bot does not miss the response window.

A small command surface is an advantage, not a limitation. A /ping command tells you far more than a clever feature that fails in three different places. Once that path works, split the Discord-specific wiring from your actual business logic. Keep the event handlers thin, keep state out of them where possible, and move the core work into separate modules or services so the bot is easier to test, debug, and redeploy.

One more practical point. If you are storing configuration, token handling, or environment-specific secrets, keep that discipline separate from command code and apply the same habits you would use for any other credentialed service. A good reference for that kind of setup is credential management for application secrets.

Keeping Your Token and Secrets Safe

The token is the easiest thing to leak and the hardest thing to excuse. Treat it like a password from the first minute. Put it in a .env file for local development, add that file to .gitignore, and never hard-code it into source or paste it into a screenshot.

Treat the token like a password

On your host, use environment variables or a real secrets manager. The exact mechanism depends on the platform, but the rule doesn't change, secrets belong outside the repository. If the token ever gets committed, regenerate it immediately in the Developer Portal and assume the old one is compromised. Discord's reset flow exists for exactly that reason.

A bot repo that ships with a live token in commit history is already exposed. GitHub's secret scanning and automated indexing make that mistake very hard to keep private, even if you delete the file later. That's why the pre-deploy checklist starts with the repository itself, not the hosting provider.

A clean baseline looks like this:

  • Store locally in .env: Keep the token out of code and out of shell history when possible.
  • Ignore secret files: Make sure .env, credential dumps, and export files are excluded from Git.
  • Rotate on exposure: Regenerate the Discord token the moment it appears in a repo, chat log, or issue.
  • Use host secrets: Put production values into the platform's secret store or protected environment variables.
  • Review diffs before merge: A config file that looks harmless can still expose live credentials.

For broader credential hygiene patterns, the guidance in credential management practices is a good companion read, especially if you're already juggling API keys for other services.

Handling Rate Limits and Retries Gracefully

A bot that works for a small private server can still fail under a burst of traffic. The usual culprit is rate limiting. Discord uses route-based buckets, so some endpoints are throttled independently while others share capacity. The safe mental model is simple. Don't spam requests, read the response headers, and back off when Discord tells you to wait.

Back off instead of spamming retries

Most libraries help here. Both discord.py and discord.js handle ordinary retries for you, which is enough for a lot of bots. If you're sending bursts of messages, editing embeds in a loop, or fan-out posting into many channels, you may need a queue so your own code doesn't create a retry storm.

The behavior to watch is the Retry-After header. When Discord returns a 429, the correct move is to pause for that route and then continue, not to hammer the same endpoint again. Batch work whenever you can. One well-formed API call is better than a loop that sleeps between dozens of tiny calls.

Common Rate Limit Buckets for Bots Typical bucket Recommended handling
Channel messages Per channel route Queue sends, preserve order
Message edits Per message route Coalesce updates before sending
Interaction responses Per interaction Reply once, defer if work is slow
Member or role updates Per guild route Batch administrative changes

For a deeper look at the retry side of the problem, the practical patterns in API rate limits are useful, even outside Discord.

The goal isn't to eliminate every 429. The goal is to make one 429 boring, recoverable, and invisible to users.

A good implementation choice is to let the library cover normal traffic, then add your own queue only when the bot has a clear throughput problem. That keeps the code simple until complexity is justified.

Hosting Options Compared for Real Bots

A Discord bot needs to stay online, restart cleanly, and load its secrets from the environment every time it boots. That sounds simple until the bot starts handling real traffic, because sleeping hosts, brittle redeploys, and messy process recovery turn small mistakes into missed commands. The right setup is the one that keeps the process alive, makes failure obvious, and lets you fix it without guessing.

Pick infrastructure that stays online

A VPS gives you the most control. You can run systemd, PM2, Docker, or another supervisor that fits your stack, inspect logs directly when something fails, and control restarts without waiting on a platform abstraction. For serious bots or anything with paying users, this is usually the safest default because uptime, environment handling, and process recovery stay in your hands.

A PaaS like Railway, Render, or Fly is easier to launch early. You push code, set secrets, and let the platform handle the deploy mechanics. That works well for hobby bots and early prototypes, especially when the bot is light on CPU and memory. The trade-off is less direct debugging when the bot starts acting flaky, since the platform hides part of the runtime behavior.

A container platform sits between those options. It gives you packaging discipline, predictable runtime behavior, and a cleaner path if the project grows. It also fits teams that already work with images, health checks, and restart policies. If you already build service-oriented systems, the patterns in building microservices for startups carry over well to a bot that needs the same operational habits. For teams that expect growth without a full platform rewrite, vertical scaling planning is the better lens than treating the bot like a throwaway script.

A Raspberry Pi still has a place for hobbyists. It works for a small private community if you are fine managing the machine yourself and handling the occasional local failure. It is a poor choice for a bot that needs strong uptime or clean redeploys.

The day-2 work matters more than the initial deploy. Use structured logging so you can filter by guild or command, add a lightweight health endpoint if your host supports it, and run an uptime monitor that checks the process on a schedule. Once a bot grows enough to need sharding, tag logs with the shard identifier so you can isolate failures quickly. The work stops being about a clever command and starts being about service management.

The right host is the one you can restart safely at 2 a.m. without wondering whether the token is still valid, the process is still alive, or the logs are readable.

Use a simple rule. A small private bot can run on a PaaS or a Pi. A business-critical or user-facing bot should live on a VPS or in a containerized deployment with restart policy, secrets management, and monitoring. Free dynos that sleep are a bad fit for a bot that is expected to answer consistently.

If you want the infrastructure around your bot to behave more like the rest of your backend, use a host that keeps secrets, restarts, and queues under control, then build from there. For teams that prefer one place to coordinate that work, Mallary.ai fits that operational style.

Official platform partners

Meta Business Partner TikTok Marketing Partner LinkedIn Marketing Partner Pinterest Business Partner X Official Partner
Start Scaling Today

Create once. Publish everywhere.

Mallary helps serious creators publish videos, images, and posts across TikTok, Instagram, YouTube, Facebook, X, LinkedIn, Pinterest, and Threads - without manually uploading to every platform.