Skip to content

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.

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 server

The filename must match the discord.js event name exactly — casing included.

src/events/guildMemberAdd.ts
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.

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}`);
});

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.ts
src/events/clientReady.ts
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`);
});

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);
});

Popii inspects your event filenames and enables the minimum required intents:

Event fileIntent enabled
guildMemberAdd.tsGuildMembers
presenceUpdate.tsGuildPresences
messageCreate.tsMessageContent + GuildMessages
messageReactionAdd.tsGuildMessageReactions
voiceStateUpdate.tsGuildVoiceStates

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,
],
});