Commands
Commands are the core of your Discord bot — they're the slash commands users see and invoke.
Defining a command
Use a CommandRouter and the @command decorator:
from fastapi_interactions.commands import CommandRouter
router = CommandRouter()
@router.command(name="ping", description="Check bot latency")
async def ping(ctx):
return "Pong!"
Every command is an async function that receives a Context object (ctx) as its first parameter. The context provides access to the invoking user, the guild/channel where the command was invoked, and the options they supplied.
Register the router with your bot:
Adding options
Options are the parameters users fill in when invoking a command. Use the Option class with type-specific static methods:
from fastapi_interactions.commands import CommandRouter, Option
@router.command(name="greet", description="Greet someone")
@Option.string(name="name", description="Who to greet", required=True)
async def greet(ctx, name: str):
return f"Hello, {name}!"
The parameter name in your function (name: str) must match the option name you register (name="name"). This is how the framework automatically binds option values to function parameters.
Multiple options
@router.command(name="calculate", description="Add two numbers")
@Option.integer(name="a", description="First number", required=True)
@Option.integer(name="b", description="Second number", required=True)
async def calculate(ctx, a: int, b: int):
return f"{a} + {b} = {a + b}"
Option values are passed directly to your function as keyword arguments. No manual lookups needed.
Optional options
Use Python's default parameter syntax:
@router.command(name="search", description="Search for something")
@Option.string(name="query", description="What to search for", required=True)
@Option.integer(name="limit", description="Result limit", required=False)
async def search(ctx, query: str, limit: int = 10):
return f"Searching for {query!r} (limit: {limit})"
Required options must be declared before optional ones. Discord rejects command registrations that violate this ordering.
Available option types
Each option type has a corresponding Option static method:
| Method | Type | Python type |
|---|---|---|
Option.string() |
STRING | str |
Option.integer() |
INTEGER | int |
Option.number() |
NUMBER | float |
Option.boolean() |
BOOLEAN | bool |
Option.user() |
USER | str (snowflake) |
Option.channel() |
CHANNEL | str (snowflake) |
Option.role() |
ROLE | str (snowflake) |
Option.mentionable() |
MENTIONABLE | str (snowflake) |
from fastapi_interactions.commands import CommandRouter, Option
@router.command(name="configure", description="Configure settings")
@Option.integer(name="count", description="How many", required=True)
@Option.boolean(name="enabled", description="Enable it", required=True)
async def configure(ctx, count: int, enabled: bool):
return f"Count: {count}, Enabled: {enabled}"
Accessing context
The ctx parameter gives you information about the interaction:
@router.command(name="whoami", description="Who are you")
async def whoami(ctx):
user = ctx.user.username
guild = ctx.guild_id or "DM"
channel = ctx.channel_id or "N/A"
return f"You're {user} in {guild}:{channel}"
ctx.user — The user who invoked the command (always available).
ctx.guild_id — The ID of the guild, or None if invoked in a DM.
ctx.channel_id — The ID of the channel where the command was invoked.
ctx.get_option_value(name, default=None) — Look up an option value by name. Used for dynamic lookups when you need to check if an optional option was provided:
@router.command(name="config", description="Configure settings")
@Option.string(name="setting", description="Setting name", required=True)
@Option.string(name="value", description="Setting value", required=False)
async def config(ctx, setting: str):
value = ctx.get_option_value("value")
if value is None:
return f"Current {setting}: (not set)"
return f"Set {setting} to {value}"
Returning responses
The simplest response is a plain string:
For more control, return a response object. See Responses for the full reference.
Decorator order
Decorators stack bottom-up. Make sure @Option.*() appears below @router.command():
@router.command(name="echo", description="Echo text")
@Option.string(name="text", description="Text to echo", required=True)
async def echo(ctx, text: str):
return text
If you put @router.command() on the bottom, the decorators run in the wrong order and your options won't be attached.