Flora lets you register handlers for Discord gateway events using the on() function.
Basic Usage
on('ready', async (ctx) => {
console.log('bot ready')
})
on('messageCreate', async (ctx) => {
if (ctx.msg.author?.bot) return
if (ctx.msg.content === '!hello') {
await ctx.reply('hello!')
}
})
Available Events
Fired when the bot connects to Discord.
Fired when a message is created.
Fired when a message is edited.
Fired when a message is deleted.
Fired when multiple messages are deleted at once.
Fired when a slash command or component interaction is created.
componentInteraction
ComponentInteractionContext
Fired when a button or select menu is used.
Fired when a modal form is submitted.
Fired when a reaction is added to a message.
Fired when a reaction is removed from a message.
Fired when all reactions of a specific emoji are removed.
Fired when all reactions are removed from a message.
Event Context
Every event handler receives a context object:
type BaseContext<TPayload> = {
msg: TPayload // Event payload
reply: (content) => Promise<void> // Send a reply
edit: (content) => Promise<void> // Edit the message/response
}
Message Events
Message Create
on('messageCreate', async (ctx) => {
// Ignore bot messages
if (ctx.msg.author?.bot) return
// Check content
if (ctx.msg.content?.includes('hello')) {
await ctx.reply('Hi there!')
}
})
Always check ctx.msg.author?.bot to avoid responding to other bots.
Message Update
on('messageUpdate', async (ctx) => {
console.log('message updated', ctx.msg.id)
// ctx.msg contains the updated message data
})
Message Delete
on('messageDelete', async (ctx) => {
console.log('message deleted', ctx.msg.id)
// ctx.msg.id is the deleted message ID
})
Bulk Delete
on('messageDeleteBulk', async (ctx) => {
console.log('bulk delete', ctx.msg.ids.length)
// ctx.msg.ids is an array of deleted message IDs
})
Interaction Events
Interaction Create
Fired for slash commands and component interactions:
on('interactionCreate', async (ctx) => {
console.log('interaction', ctx.msg.commandName)
// This is also handled by slash command handlers
})
Slash commands registered with slash() automatically handle interactionCreate. Use this event only for custom interaction handling.
Component Interactions
on('componentInteraction', async (ctx) => {
// Handle button clicks and select menu choices
const customId = ctx.msg.data?.custom_id
if (customId === 'delete_button') {
await ctx.reply({ content: 'Deleted!', ephemeral: true })
}
})
Modal Submissions
on('modalSubmit', async (ctx) => {
const customId = ctx.msg.data?.custom_id
if (customId === 'feedback_form') {
// Extract form values
await ctx.reply({ content: 'Thanks for your feedback!', ephemeral: true })
}
})
Reaction Events
Reaction Add
on('reactionAdd', async (ctx) => {
const emoji = ctx.msg.emoji
if (emoji?.name === '⭐') {
console.log('Star reaction added!')
}
})
Reaction Remove
on('reactionRemove', async (ctx) => {
console.log('Reaction removed:', ctx.msg.emoji?.name)
})
Reaction Remove All
on('reactionRemoveAll', async (ctx) => {
console.log('All reactions removed from message:', ctx.msg.messageId)
})
Ready Event
Fired once when the bot connects:
on('ready', async (ctx) => {
console.log('Bot is ready!')
// Initialize resources, log start time, etc.
})
Multiple Handlers
You can register multiple handlers for the same event:
on('messageCreate', async (ctx) => {
// First handler: logging
console.log('Message from', ctx.msg.author?.username)
})
on('messageCreate', async (ctx) => {
// Second handler: auto-moderation
if (ctx.msg.content?.includes('spam')) {
// Handle spam
}
})
Handlers execute sequentially in registration order. Use await for async operations to maintain order.
Examples
Auto-responder
on('messageCreate', async (ctx) => {
if (ctx.msg.author?.bot) return
const content = ctx.msg.content?.toLowerCase()
if (content?.includes('good morning')) {
await ctx.reply('Good morning! ☀️')
} else if (content?.includes('good night')) {
await ctx.reply('Good night! 🌙')
}
})
Reaction Role System
on('reactionAdd', async (ctx) => {
const emoji = ctx.msg.emoji?.name
const messageId = ctx.msg.messageId
// Check if it's the role message
if (messageId === 'YOUR_ROLE_MESSAGE_ID') {
if (emoji === '🔴') {
// Grant red role
} else if (emoji === '🔵') {
// Grant blue role
}
}
})
on('reactionRemove', async (ctx) => {
// Remove role when reaction is removed
})
Message Logger
const store = kv.store('message_log')
on('messageCreate', async (ctx) => {
if (ctx.msg.author?.bot) return
const logEntry = JSON.stringify({
id: ctx.msg.id,
author: ctx.msg.author?.username,
content: ctx.msg.content,
timestamp: new Date().toISOString()
})
await store.set(`msg:${ctx.msg.id}`, logEntry)
})
on('messageDelete', async (ctx) => {
// Retrieve deleted message from log
const logged = await store.get(`msg:${ctx.msg.id}`)
if (logged) {
console.log('Deleted message:', logged)
}
})
Best Practices
Filter bot messages
Always check ctx.msg.author?.bot in message handlers to avoid loops.
Use async/await
Properly await async operations to ensure handlers complete before the next event.
Handle errors
Wrap handlers in try/catch to prevent one event from breaking others.
Keep handlers lightweight
Heavy processing should be deferred or cached. Event handlers should be fast.