Documentation Index
Fetch the complete documentation index at: https://docs.towns.com/llms.txt
Use this file to discover all available pages before exploring further.
Slash commands are special message types that:
- Start with
/ (e.g., /help, /poll, /weather)
- Show up in autocomplete when users type
/ in Towns
- Display descriptions to help users understand what they do
- Are handled separately from regular messages (don’t trigger
onMessage)
Example Commands
/help → Show bot commands
/ban @john → Ban a user
/weather San Francisco → Get weather for a location
/remind 5m Check the oven → Set a reminder
Creating commands
Commands are defined in a TypeScript file (typically src/commands.ts):
import type { BotCommand } from '@towns-protocol/bot'
export const commands = [
{
name: 'help',
description: 'Show available bot commands'
},
{
name: 'ping',
description: 'Check if bot is responding'
},
{
name: 'greet',
description: 'Greet a user'
},
{
name: 'weather',
description: 'Get current weather for a location'
}
] as const satisfies BotCommand[]
export default commands
Handling Commands
You can use bot.onSlashCommand() to handle each slash command:
import { makeTownsBot } from '@towns-protocol/bot'
import commands from './commands'
const bot = await makeTownsBot(appPrivateData, jwtSecret, { commands })
// Handle /help command
bot.onSlashCommand('help', async (handler, event) => {
const commandList = commands
.map(cmd => `• \`/${cmd.name}\` - ${cmd.description}`)
.join('\n')
await handler.sendMessage(
event.channelId,
`**Available Commands**\n${commandList}`
)
})
// Handle /ping command
bot.onSlashCommand('ping', async (handler, event) => {
const latency = Date.now() - event.createdAt.getTime()
await handler.sendMessage(event.channelId, `Pong! 🏓 (${latency}ms)`)
})
Working with Arguments
Slash commands can receive arguments after the command name.
You can get them from the args property of the event.
bot.onSlashCommand('weather', async (handler, { args, channelId }) => {
// /weather San Francisco
// args: ['San', 'Francisco']
const location = args.join(' ')
if (!location) {
await handler.sendMessage(
channelId,
'Usage: /weather <location>\nExample: /weather San Francisco'
)
return
}
const weather = await callWeatherAPI(location)
await handler.sendMessage(
channelId,
`Weather in ${location}: ${weather.temp}°F, ${weather.conditions}`
)
})
Working with Mentions
Users can mention other users or the bot in slash commands.
You can get the mentions from the mentions property of the event.
// /greet @john @jane
bot.onSlashCommand('greet', async (handler, { channelId, mentions}) => {
if (mentions.length === 0) {
await handler.sendMessage(
channelId,
'Usage: /greet @user1 @user2 ...'
)
return
}
const greetings = mentions
.map(m => `Hello <@${m.userId}>!`)
.join('\n')
await handler.sendMessage(channelId, greetings, { mentions })
})
Checking Permissions
You may want to check if the user has the necessary permissions to use a command.
You can use the hasAdminPermission method to check if the user is an admin.
This is a shortcut for checking if the user has the ModifyBanning permission.
If you want to check for a specific permission, you can use the checkPermission method.
bot.onSlashCommand('ban', async (handler, { userId, spaceId, channelId, mentions }) => {
// Check if user is admin
const isAdmin = await handler.hasAdminPermission(userId, spaceId)
if (!isAdmin) {
await handler.sendMessage( channelId, '❌ Only admins can use this command')
return
}
const userToBan = mentions[0]?.userId
if (!userToBan) {
await handler.sendMessage( channelId, 'Usage: /ban @user')
return
}
try {
await handler.ban(userToBan, spaceId)
await handler.sendMessage(
channelId,
`✓ Banned <@${userToBan}>`
)
} catch {
await handler.sendMessage( channelId, '❌ Failed to ban user')
}
})
Paid Commands
You can create commands that require USDC payments in any network. Payments are processed through the x402 protocol.
Defining Paid Commands
Add a paid property to your command definition with a price in USDC:
import type { BotCommand } from '@towns-protocol/bot'
export const commands = [
{
name: 'generate',
description: 'Generate AI content',
paid: { price: '$0.20' }
}
] as const satisfies BotCommand[]
export default commands
How it works
When a user invokes a paid command:
- Bot sends a signature request for USDC transfer authorization
- User signs the request in their wallet
- Payment is verified and settled via the x402 facilitator
- Your command handler executes after successful payment
export const commands = [
{
name: 'generate',
description: 'Generate AI content',
paid: { price: '$0.20' }
}
]
// Handler only runs after payment succeeds
bot.onSlashCommand('generate', async (handler, event) => {
// Payment already processed at this point
await handler.sendMessage(
event.channelId,
'Thank you for your purchase! Here is your generated content...'
)
})
Updating Commands
Commands are automatically synced with Towns when your bot starts.
To update your bot’s slash commands:
- Modify your command definitions in
src/commands.ts
- Restart your bot
The new commands will be automatically registered and appear in Towns’ autocomplete.
Next Steps