Skip to main content

Overview

Flora provides helper functions for common tasks like checking roles and extracting subcommand information.

hasRole()

Check if a user has a specific role in the current guild.

Signature

function hasRole(ctx: InteractionContext, roleId: string): boolean

Parameters

ctx
InteractionContext
required
The interaction context from a slash command
roleId
string
required
The Discord role ID to check for

Return Value

Returns true if the user has the role, false otherwise.

Example

slash({
  name: 'admin',
  description: 'Admin-only command',
  run: async (ctx) => {
    const adminRoleId = '123456789'
    
    if (!hasRole(ctx, adminRoleId)) {
      await ctx.reply({ content: 'You need admin role to use this!', ephemeral: true })
      return
    }
    
    await ctx.reply('Admin command executed!')
  }
})

Multiple Roles

slash({
  name: 'moderator',
  description: 'Moderator command',
  run: async (ctx) => {
    const modRoleId = '111111111'
    const adminRoleId = '222222222'
    
    if (!hasRole(ctx, modRoleId) && !hasRole(ctx, adminRoleId)) {
      await ctx.reply({ content: 'Insufficient permissions', ephemeral: true })
      return
    }
    
    await ctx.reply('Access granted!')
  }
})

getSubcommand()

Extract the name of the subcommand that was invoked.

Signature

function getSubcommand(ctx: InteractionContext): string | undefined

Parameters

ctx
InteractionContext
required
The interaction context from a slash command

Return Value

Returns the subcommand name as a string, or undefined if no subcommand was used.

Example

slash({
  name: 'admin',
  description: 'Admin commands',
  subcommands: [
    {
      name: 'ban',
      description: 'Ban a user',
      run: async (ctx) => {
        await ctx.reply('User banned')
      }
    },
    {
      name: 'kick',
      description: 'Kick a user',
      run: async (ctx) => {
        await ctx.reply('User kicked')
      }
    }
  ]
})

// In a global interaction handler
on('interactionCreate', async (ctx) => {
  const subcommand = getSubcommand(ctx)
  console.log(`Subcommand used: ${subcommand}`) // 'ban' or 'kick'
})

Manual Subcommand Routing

slash({
  name: 'config',
  description: 'Bot configuration',
  options: [
    {
      name: 'prefix',
      description: 'Change prefix',
      type: 'subcommand',
      options: [{ name: 'new_prefix', type: 'string', required: true }]
    },
    {
      name: 'channel',
      description: 'Set log channel',
      type: 'subcommand',
      options: [{ name: 'channel_id', type: 'string', required: true }]
    }
  ],
  run: async (ctx) => {
    const sub = getSubcommand(ctx)
    
    if (sub === 'prefix') {
      const newPrefix = ctx.options.new_prefix
      await ctx.reply(`Prefix changed to ${newPrefix}`)
    } else if (sub === 'channel') {
      const channelId = ctx.options.channel_id
      await ctx.reply(`Log channel set to <#${channelId}>`)
    }
  }
})

getSubcommandGroup()

Extract the name of the subcommand group that was invoked (for nested subcommands).

Signature

function getSubcommandGroup(ctx: InteractionContext): string | undefined

Parameters

ctx
InteractionContext
required
The interaction context from a slash command

Return Value

Returns the subcommand group name as a string, or undefined if no group was used.

Example

// Command structure: /settings user display-name
// Group: 'user', Subcommand: 'display-name'

slash({
  name: 'settings',
  description: 'Bot settings',
  options: [
    {
      name: 'user',
      description: 'User settings',
      type: 'subcommand_group',
      options: [
        {
          name: 'display-name',
          type: 'subcommand',
          description: 'Set display name',
          options: [{ name: 'name', type: 'string', required: true }]
        },
        {
          name: 'theme',
          type: 'subcommand',
          description: 'Set theme'
        }
      ]
    }
  ],
  run: async (ctx) => {
    const group = getSubcommandGroup(ctx)
    const sub = getSubcommand(ctx)
    
    console.log(`Group: ${group}, Subcommand: ${sub}`)
    // Group: user, Subcommand: display-name
    
    if (group === 'user' && sub === 'display-name') {
      await ctx.reply(`Name updated to ${ctx.options.name}`)
    }
  }
})

Common Patterns

Role-Based Permission Check

function requireRole(ctx: InteractionContext, roleId: string, roleName: string): boolean {
  if (!hasRole(ctx, roleId)) {
    ctx.reply({ 
      content: `❌ You need the ${roleName} role to use this command.`,
      ephemeral: true 
    })
    return false
  }
  return true
}

slash({
  name: 'purge',
  description: 'Delete messages',
  run: async (ctx) => {
    if (!requireRole(ctx, 'MOD_ROLE_ID', 'Moderator')) return
    
    // Command logic
    await ctx.reply('Messages purged')
  }
})

Combining Helpers

slash({
  name: 'admin',
  description: 'Admin panel',
  subcommands: [
    {
      name: 'reset',
      description: 'Reset settings',
      run: async (ctx) => {
        // Check role
        if (!hasRole(ctx, 'ADMIN_ROLE_ID')) {
          await ctx.reply({ content: 'Admin only!', ephemeral: true })
          return
        }
        
        // Execute reset
        const sub = getSubcommand(ctx)
        console.log(`Admin ${ctx.msg.user.username} used ${sub}`)
        
        await ctx.reply('Settings reset!')
      }
    }
  ]
})

Dynamic Subcommand Logging

on('interactionCreate', async (ctx) => {
  const group = getSubcommandGroup(ctx)
  const sub = getSubcommand(ctx)
  
  if (group && sub) {
    console.log(`/${ctx.msg.commandName} ${group} ${sub}`)
  } else if (sub) {
    console.log(`/${ctx.msg.commandName} ${sub}`)
  } else {
    console.log(`/${ctx.msg.commandName}`)
  }
})

Notes

  • hasRole() only works with InteractionContext (slash commands), not message commands
  • Role IDs are strings, not numbers
  • getSubcommand() returns the first-level subcommand name
  • getSubcommandGroup() is for nested command structures (group → subcommand)
  • These helpers safely return undefined if data is missing
  • Always check permissions before executing sensitive commands

Build docs developers (and LLMs) love