Guide

Commands

Create and organize slash commands, user context menus and message context menus with GlyriaCommand.

Overview

Commands in glyria.js are file-based. Every file in src/commands/ is automatically loaded and registered on Discord at startup — no manual registration needed.

Each command file exports a command instance as default:

// src/commands/ping.ts
export default new GlyriaCommand()
  .setName("ping")
  .setDescription("Pong!")
  .execute(async (ctx) => {
    await ctx.reply({ content: "Pong!" })
  })
GlyriaCommand, GlyriaUserCommand and GlyriaMessageCommand are available globally — no import needed.

Slash commands

Options

Add typed options to your command with the add*Option methods:

export default new GlyriaCommand()
  .setName("hello")
  .setDescription("Say hello to someone")
  .addUserOption((option) =>
    option
      .setName("user")
      .setDescription("The user to greet")
      .setRequired(true)
  )
  .addStringOption((option) =>
    option
      .setName("message")
      .setDescription("A custom message")
      .setRequired(false)
  )
  .execute(async (ctx) => {
    await ctx.reply({ content: "Hello!" })
  })

Available option types

MethodType
addStringOptionText input
addIntegerOptionInteger number
addNumberOptionDecimal number
addBooleanOptionTrue / False
addUserOptionDiscord user
addRoleOptionDiscord role

Each option exposes:

MethodDescription
.setName(name)Option name (lowercase, no spaces)
.setDescription(desc)Option description
.setRequired(bool)Whether the option is required

Subcommands

Group related actions under a single command with .addSubCommand():

export default new GlyriaCommand()
  .setName("moderation")
  .setDescription("Moderation commands")
  .addSubCommand((cmd) =>
    cmd
      .setName("ban")
      .setDescription("Ban a user")
      .addUserOption((option) =>
        option
          .setName("user")
          .setDescription("User to ban")
          .setRequired(true)
      )
      .addStringOption((option) =>
        option
          .setName("reason")
          .setDescription("Reason of the ban")
          .setRequired(false)
      )
      .execute(async (ctx) => {
        // handle ban
      })
  )
  .execute(async (ctx) => {
    // handle base command
  })

Subcommand groups

For deeper organization, group subcommands with .addSubCommandGroup():

export default new GlyriaCommand()
  .setName("moderation")
  .setDescription("Moderation commands")
  .addSubCommand((cmd) =>
    cmd
      .setName("ban")
      .setDescription("Ban a user")
      .addUserOption((option) =>
        option
          .setName("user")
          .setDescription("User to ban")
          .setRequired(true)
      )
      .addStringOption((option) =>
        option
          .setName("reason")
          .setDescription("Reason of the ban")
          .setRequired(false)
      )
      .execute((ctx) => {
        console.log("ban command")
      })
  )
  .addSubCommandGroup((group) =>
    group
      .setName("config")
      .setDescription("Configuration commands")
      .addSubCommand((cmd) =>
        cmd
          .setName("logs")
          .setDescription("Configure logs")
          .addBooleanOption((option) =>
            option
              .setName("enabled")
              .setDescription("Enable logs")
              .setRequired(true)
          )
          .execute(async (ctx) => {
            // handle logs config
          })
      )
  )
  .execute((ctx) => {
    ctx.reply({ content: "Moderation command executed!" })
  })

This generates the following Discord command structure:

/moderation ban <user> [reason]
/moderation config logs <enabled>

Context menus

Context menus appear when a user right-clicks on a user or a message, under the Apps section. They have no description, no options, and no subcommands.

User context menu

Triggered by right-clicking on a user. The handler receives a UserContextMenuCommandInteraction — the targeted user is available via ctx.targetUser.

// src/commands/profile.ts
export default new GlyriaUserCommand()
  .setName("View profile")
  .execute(async (ctx) => {
    const user = ctx.targetUser
    await ctx.reply({
      content: `Profile of ${user.username}`,
      flags: djs.MessageFlags.Ephemeral,
    })
  })

Message context menu

Triggered by right-clicking on a message. The handler receives a MessageContextMenuCommandInteraction — the targeted message is available via ctx.targetMessage.

// src/commands/translate.ts
export default new GlyriaMessageCommand()
  .setName("Translate")
  .execute(async (ctx) => {
    const message = ctx.targetMessage
    await ctx.reply({
      content: `Translating: "${message.content}"`,
      flags: djs.MessageFlags.Ephemeral,
    })
  })

Permissions

Restrict any command to members with specific Discord permissions using .setPermissions(). Works on GlyriaCommand, GlyriaUserCommand and GlyriaMessageCommand:

export default new GlyriaCommand()
  .setName("ban")
  .setDescription("Ban a user")
  .setPermissions(djs.PermissionsBitField.Flags.BanMembers)
  .execute(async (ctx) => {
    // only members with BanMembers permission can use this
  })
PermissionsBitField and MessageFlags are available via the djs namespace — no import needed.

Metadata

Attach custom data to a command with .setMetaData(). Works on GlyriaCommand, GlyriaUserCommand and GlyriaMessageCommand. Glyria stores it as-is and never interprets it — it's entirely up to you.

export default new GlyriaCommand()
  .setName("ban")
  .setDescription("Ban a user")
  .setMetaData({
    category: "moderation",
    cooldown: 5000,
    premium: false,
    guildOnly: true,
  })
  .execute(async (ctx) => {
    // your logic here
  })

useCommands()

useCommands() returns the full list of loaded commands with their metadata. It's available globally and always reflects the current state of the bot.

const commands = useCommands()
// [
//   { name: "ban", description: "Ban a user", meta: { category: "moderation", cooldown: 5000 } },
//   { name: "ping", description: "Pong!", meta: {} },
//   { name: "View profile", description: "", meta: {} },
// ]

A common use case is building a /help command organized by category:

export default new GlyriaCommand()
  .setName("help")
  .setDescription("List all commands")
  .execute(async (ctx) => {
    const commands = useCommands()

    const byCategory = commands.reduce((acc, cmd) => {
      const cat = (cmd.meta.category as string) ?? "general"
      acc[cat] ??= []
      acc[cat].push(cmd)
      return acc
    }, {} as Record<string, typeof commands>)

    // build your embed from byCategory
  })

Organizing files

Subfolders inside src/commands/ are for organization only — every file at any depth is loaded automatically.

src/commands/
  ping.ts
  moderation/
    ban.ts
    kick.ts
  config/
    advanced/
      logs.ts
  context-menus/
    profile.ts
    translate.ts
Keep one command instance per file. Use subcommands and subcommand groups to group related actions inside a GlyriaCommand.
Copyright © 2026