Events
Each file in src/events/ listens to one Discord gateway event. The filename is the event name — that’s the entire configuration. Popii reads the filenames to determine which intents to request, so you never have to touch the intent list manually.
The convention
Section titled “The convention”src/events/ guildMemberAdd.ts → fires when a member joins messageCreate.ts → fires on every new message guildCreate.ts → fires when the bot joins a new serverThe filename must match the discord.js event name exactly — casing included.
Basic event
Section titled “Basic event”import { event } from "popii-framework";
export default event(async (pop, member) => { const channel = member.guild.systemChannel; if (channel) { await channel.send(`Welcome to ${member.guild.name}, ${member}!`); }});The first argument is always EventPop — the Popii context object with helpers for caching, i18n, and client access. The remaining arguments are the raw discord.js event payload, fully typed based on the event name.
Accessing the client and logging
Section titled “Accessing the client and logging”import { event } from "popii-framework";
export default event(async (pop, guild) => { pop.log.info(`Joined ${guild.name} — ${guild.memberCount} members`);
// Full discord.js access when you need it const owner = await guild.fetchOwner(); pop.log.debug(`Owner: ${owner.user.tag}`);});Multiple handlers for one event
Section titled “Multiple handlers for one event”Use a subfolder to split a single event across multiple files. All files in the folder run for that event:
src/events/ messageCreate/ spam-filter.ts ← runs first (alphabetical) xp-reward.ts automod.tsThe clientReady event
Section titled “The clientReady event”import { event } from "popii-framework";
export default event(async (pop) => { pop.log.info(`Online as ${pop.client.discord.user?.tag}`); pop.log.info(`Serving ${pop.client.discord.guilds.cache.size} guilds`);});Using pop helpers in events
Section titled “Using pop helpers in events”EventPop provides the same utilities available in command handlers:
import { event } from "popii-framework";
export default event(async (pop, message) => { if (message.author.bot) return;
// In-process cache with TTL const profile = await pop.cache(`profile:${message.author.id}`, 60_000, async () => { return db.query("SELECT * FROM users WHERE id = ?").get(message.author.id); });
// Persistent KV store (Redis-backed or in-process) await pop.cooler.set(`last_seen:${message.author.id}`, Date.now(), 86_400_000);});Automatic intent detection
Section titled “Automatic intent detection”Popii inspects your event filenames and enables the minimum required intents:
| Event file | Intent enabled |
|---|---|
guildMemberAdd.ts | GuildMembers |
presenceUpdate.ts | GuildPresences |
messageCreate.ts | MessageContent + GuildMessages |
messageReactionAdd.ts | GuildMessageReactions |
voiceStateUpdate.ts | GuildVoiceStates |
To override auto-detection completely, pass intents directly to popiiClient():
import { GatewayIntentBits } from "discord.js";
popiiClient({ token: process.env.DISCORD_TOKEN!, intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, ],});