Skip to main content
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

ready
BaseContext<EventReady>
Fired when the bot connects to Discord.
messageCreate
MessageContext
Fired when a message is created.
messageUpdate
MessageUpdateContext
Fired when a message is edited.
messageDelete
MessageDeleteContext
Fired when a message is deleted.
messageDeleteBulk
MessageDeleteBulkContext
Fired when multiple messages are deleted at once.
interactionCreate
InteractionContext
Fired when a slash command or component interaction is created.
componentInteraction
ComponentInteractionContext
Fired when a button or select menu is used.
modalSubmit
ModalSubmitContext
Fired when a modal form is submitted.
reactionAdd
ReactionContext
Fired when a reaction is added to a message.
reactionRemove
ReactionContext
Fired when a reaction is removed from a message.
reactionRemoveEmoji
ReactionContext
Fired when all reactions of a specific emoji are removed.
reactionRemoveAll
ReactionRemoveAllContext
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 })
  }
})
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

1

Filter bot messages

Always check ctx.msg.author?.bot in message handlers to avoid loops.
2

Use async/await

Properly await async operations to ensure handlers complete before the next event.
3

Handle errors

Wrap handlers in try/catch to prevent one event from breaking others.
4

Keep handlers lightweight

Heavy processing should be deferred or cached. Event handlers should be fast.

Build docs developers (and LLMs) love