Skip to content

Commands and options

Create commands with command() and place them under src/commands/. Command names are unique across your project and enabled plugins; Popii stops startup when two definitions collide.

A chat-input command can be invoked as both a Discord slash command and a guild text command. Both modes use the same handler, options, authorization rules, middleware, cooldowns, quotas, timeout, metrics, localization, and error boundary.

import { ApplicationCommandOptionType } from "discord.js";
import { command } from "popii-framework/commands";
export default command({
name: "greet",
description: "Greet somebody",
slash: true,
text: true,
aliases: ["hello"],
options: [
{
name: "user",
description: "Who to greet",
type: ApplicationCommandOptionType.User,
required: true,
},
],
async do(pop) {
const user = pop.options.getUser("user")!;
await pop.reply(`Hello, ${user.username}!`);
},
});

With prefix: "!", this definition accepts:

/greet user:@Cake
!greet @Cake
!hello @Cake

slash and text default to enabled for ordinary chat-input commands. Set one explicitly to false when a command only makes sense in the other mode:

export default command({
name: "say",
description: "Send a message from a text invocation",
slash: false,
text: true,
async do(pop) {
await pop.reply("Hello from a text command");
},
});

Slash registration ignores commands with slash: false. The message handler ignores commands with text: false.

Set a static prefix in popii.config.ts:

import { defineConfig } from "popii-framework";
export default defineConfig({
token: process.env.DISCORD_TOKEN!,
prefix: "!",
});

The prefix may also be a synchronous or asynchronous function. Return one prefix, several accepted prefixes, or null to disable prefix commands for that message:

export default defineConfig({
token: process.env.DISCORD_TOKEN!,
async prefix(message) {
if (!message.guildId) return null;
return message.guildId === process.env.SUPPORT_GUILD_ID
? ["!", "?"]
: "!";
},
});

Mentioning the bot also works as a prefix even when no string prefix matches:

@Popii ping

Prefix commands require Discord’s privileged Message Content Intent. Open the Discord Developer Portal, select the application, go to Bot → Privileged Gateway Intents, and enable Message Content Intent.

Popii requests the corresponding gateway intent, but Discord will reject or withhold message content unless it is enabled for the application. Slash commands do not require this privileged intent.

Text arguments are mapped to the command’s declared option order. The final string option consumes all remaining positional text.

!announce general Maintenance starts in ten minutes

Single and double quotes preserve spaces, and a backslash escapes the following character:

!tag create "release notes" "Cakemix is ready"
!tag create 'owner tools' 'Restricted workspace'

Named flags use --name=value. A flag without a value is treated as boolean true:

!announce --channel=general --message="Maintenance starts soon" --silent

The text adapter supports string, integer, number, boolean, user, channel, role, mentionable, and attachment getters. Users, roles, and channels may be supplied as Discord mentions or IDs. Attachments are read from the message in declared attachment-option order.

For a required argument that is missing, Popii prompts when the active context provides awaitMessage; otherwise it returns a localized missing-argument response. Permission and middleware checks run before any prompt.

Use pop.reply() in handlers shared by slash and text commands. It maps to the correct Discord response mechanism in either mode.

pop.defer() sends the typing indicator for a text command. pop.respond() is interaction-only and throws when called from a text command.

Do not read raw interaction fields in a dual-mode handler. Prefer pop.options, pop.user, pop.member, pop.guild, pop.channel, and pop.reply().

Text invocations resolve declared subcommands and subcommand groups from positional words:

/config moderation enable
!config moderation enable

Nested files still provide the conventional command structure, such as src/commands/config/moderation/enable.ts.

Guild managers can create custom text and role commands from Dashboard → Server → Manage → Community → Custom commands. They may enable or disable text invocation and choose text response, role add, role remove, or role toggle behavior.

Custom commands are checked when no authored text command matches. They remain guild-scoped and cannot override an authored command with the same resolved name.

Text commands honor the same declarations as slash commands:

  • guildOnly and ownerOnly
  • Discord permissions
  • Allowed and denied roles
  • Allowed channels
  • Cooldowns, quotas, and mutexes
  • Global and guild command disablement
  • User and guild blacklists
  • Middleware and input schemas
  • Execution timeouts and error handling

Bots are ignored, preventing bot-to-bot command loops.

Run popii sync after changing slash-visible names, descriptions, options, or command structure. Text-only changes do not require Discord synchronization, but the running bot must be restarted or reloaded through development mode.

Terminal window
popii sync
popii dev

See the complete compiled example under examples/commands/text.ts.