aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Discord Bots

Discord Bot Event Listener: Gateway and Intents Guide

Writing a discord bot event listener is the step that turns your bot from a static command machine into an application that reacts in real time. Discord forwards everything that happens on a server — a message being sent, a member joining, a reaction being added — to your bot as an event over a persistent WebSocket connection. In this article I walk through how the event flow works, why gateway intents are mandatory, and how to build a solid event listener structure, all with discord.js.

What is the gateway and how does the event flow work?

Discord bots actually speak over two separate channels. Actions such as sending a message or creating a channel are performed through HTTP requests against the REST API. Listening to what happens on a server, on the other hand, takes place over a persistent WebSocket connection called the Gateway.

The flow goes like this: when your bot starts, it connects to the Gateway and authenticates with its token (IDENTIFY). Discord expects regular heartbeats to make sure the connection is still alive. Once the connection is established, whenever something happens on a server Discord pushes an event packet (for example MESSAGE_CREATE) to your bot. The library catches this raw packet, parses it, and triggers the listener function you registered.

This is why an event-based bot never "asks a question and waits for an answer"; instead it receives events through a push model over a continuously open line. Your bot's architecture should be built around this reality.

Intents: the key that decides what you can listen to

Since 2020, Discord has restricted which event types bots receive through Gateway Intents. An intent is a permission flag that says "I want to receive events in this category." If you don't request events you don't need, both network traffic and memory usage drop; more importantly, you won't have access to irrelevant data.

If you don't declare intents explicitly, the related events will never reach your bot. The answer to the most common "why isn't my message event firing?" question is usually a missing intent. Some intents are considered privileged and must be enabled both in the Discord Developer Portal and declared in code:

  • GuildMembers — member join/leave, member list (privileged).
  • MessageContent — access to the text content of messages (privileged).
  • GuildPresences — online status and activity (privileged).

For bots in more than 100 servers, you must obtain verification from Discord for privileged intents. That makes keeping your bot limited to the intents it genuinely needs both good practice and a requirement for scaling.

Setting up your first event listener

A minimal bot skeleton with discord.js v14 looks like this. We create the Client object with the required intents, then attach listeners to events:

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

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

// Fires once when the connection is ready
client.once(Events.ClientReady, (c) => {
  console.log(`Logged in as: ${c.user.tag}`);
});

// Fires on every new message
client.on(Events.MessageCreate, (message) => {
  if (message.author.bot) return;            // ignore messages from bots
  if (message.content === '!ping') {
    message.reply('Pong!');
  }
});

client.login(process.env.DISCORD_TOKEN);

There are two important distinctions here. client.once runs the listener only once — ideal for lifecycle events that happen a single time, like ClientReady. client.on, in contrast, fires again on every occurrence. The message.author.bot check also prevents bots from triggering one another into an infinite loop; it's an easily overlooked but critical safeguard.

Common events and how to use them correctly

A real bot reacts not to one event but to several at once. The events you'll work with most are:

  • GuildMemberAdd — for a welcome message or auto-role when a new member joins (requires the GuildMembers intent).
  • InteractionCreate — the modern, recommended path for slash commands, buttons, and menus.
  • MessageReactionAdd — for reaction-role systems.
  • GuildCreate — when the bot is added to a new server, for setup.

In modern bots you should build command logic on InteractionCreate and slash commands rather than MessageCreate, because MessageContent is a privileged intent and Discord is not encouraging message-content-based commands in the long term.

Keeping your event structure scalable

Stacking all listeners into a single file becomes unmanageable as the bot grows. A solid approach is to build an event handler that puts each event in its own file. Each file exports the event's name and the function to run:

// events/messageCreate.js
const { Events } = require('discord.js');

module.exports = {
  name: Events.MessageCreate,
  once: false,
  execute(message) {
    if (message.author.bot) return;
    // ... logic
  },
};
// the part that auto-loads the listeners
const fs = require('node:fs');
const path = require('node:path');

const eventsPath = path.join(__dirname, 'events');
const files = fs.readdirSync(eventsPath).filter((f) => f.endsWith('.js'));

for (const file of files) {
  const event = require(path.join(eventsPath, file));
  if (event.once) {
    client.once(event.name, (...args) => event.execute(...args));
  } else {
    client.on(event.name, (...args) => event.execute(...args));
  }
}

With this structure, adding a new event is as simple as dropping a file into the events/ folder. The code stays readable, each event's responsibility is separated, and debugging becomes much easier.

Common mistakes

The most frequent traps with event listeners are: (1) Missing intents — the number-one reason an event doesn't fire. (2) Choosing only the events you genuinely need instead of listening to too many. (3) Not catching errors when awaiting network requests inside a listener; a single unhandled error can crash the bot. Wrapping listeners in try/catch and running the bot under a process manager (such as PM2) noticeably improves stability in production.

Frequently Asked Questions

Is adding the intent in code enough on its own?

For privileged intents, no. You must declare MessageContent, GuildMembers, and GuildPresences in code and enable them in the bot settings on the Discord Developer Portal. If either is missing, the events won't arrive.

What is the difference between client.on and client.once?

client.on runs the listener on every recurrence of the event; client.once runs it only the first time and then automatically removes the listener. For one-time events like ClientReady, once is the right choice.

Message content comes back empty — why?

Most likely the MessageContent intent is missing. Without this privileged intent, message.content arrives empty; content is only populated for messages where the bot is mentioned or that are DMs.

Want a solid event architecture for your bot? From gateway intents to a scalable handler structure, I build Discord bots end to end. To discuss your project, get in touch with me.

Bu kategorideki tüm yazılar →

Devamı için