Wiki
猫Table of Contents
- Commands and Plugins
- The important model first
- Plugins
- Command handlers
- Registering commands
- The easiest command example
- Commands with argument validation
- Payload handlers
- Update handlers
- The runtime flow
- Middleware placement
- Good plugin boundaries
- Examples
- Common mistakes
- Putting the slash into command names
- Treating payloads like commands
- Using AddUpdateHandler(...) for command updates
- Returning errors for normal user-facing branching
- When to use what
- Where to go next
Commands and Plugins
Russian version: Commands-and-Plugins-RU
Laniakea organizes most bot behavior through plugins.
If you understand how plugins, commands, payload handlers, and update handlers fit together, the rest of the library becomes much easier to reason about.
The important model first
The normal layering is:
Botowns runtime, update flow, logging, and API clientsPlugingroups related handlers- commands handle text commands like
/start - payload handlers handle inline-button callback payloads
- update handlers handle non-command Telegram updates
In practice, most bots start with:
- one or more plugins
- a few commands
- optional plugin middleware
- maybe payload handlers once inline keyboards appear
Plugins
A plugin is a named group of:
- commands
- payload handlers
- update handlers
- shared middleware
- optional plugin logger and close hook
Example:
plugin := laniakea.NewPlugin[laniakea.NoData]("admin")
Use plugins to group functionality by concern:
adminpaymentsprofilesupport
That keeps command registration and middleware ownership clear.
Command handlers
The command handler signature is:
func(ctx *laniakea.MessageContext, db T) error
Where:
ctxis the current message/update contextdbis the dependency value of the bot’s generic typeT
Return:
nilon successerrorwhen the centralized bot error flow should handle failure
Example:
func start(ctx *laniakea.MessageContext, db *App) error {
ctx.Answer("Welcome")
return nil
}
Registering commands
Create a command with NewCommand(...) and add it to a plugin:
plugin := laniakea.NewPlugin[*App]("main")
plugin.AddCommand(plugin.NewCommand(start, "start"))
The command name:
- must not include the slash
- is matched against the parsed command token
So:
"start"matches/start"help"matches/help
The easiest command example
func echo(ctx *laniakea.MessageContext, db laniakea.NoData) error {
if ctx.Text == "" {
ctx.Answer("Usage: /echo <text>")
return nil
}
ctx.Answer(ctx.Text)
return nil
}
plugin.AddCommand(plugin.NewCommand(echo, "echo"))
For /echo hello world:
ctx.Text == "hello world"ctx.Args == []string{"hello", "world"}
Commands with argument validation
You can declare command arguments using CommandArg.
Example:
plugin.AddCommand(
plugin.NewCommand(banUser, "ban",
laniakea.NewCommandArg("user_id").
SetValueType(laniakea.CommandValueIntType).
SetRequired(),
),
)
This lets the framework validate:
- required argument presence
- integer/string/bool shape
- custom regex-based restrictions through the argument configuration
If validation fails, the command does not run and the bot error path is used.
Payload handlers
Payload handlers are for callback data coming from inline keyboard buttons.
Register them with NewPayload(...) or AddPayload(...):
func confirmDelete(ctx *laniakea.MessageContext, db *App) error {
ctx.EditCallback("Deleted", nil)
return nil
}
plugin.AddPayload(plugin.NewPayload(confirmDelete, "delete.confirm"))
Payload handlers:
- are triggered by callback payload command names
- use the same handler signature as normal commands
- receive parsed payload args in
ctx.Args
Use payloads when the trigger source is a button press, not a text command.
Update handlers
Update handlers are for Telegram updates outside the normal command/payload flow.
Register them with AddUpdateHandler(...):
plugin.AddUpdateHandler(tgapi.UpdateTypeInlineQuery, func(ctx *laniakea.MessageContext, db *App) error {
// handle inline query here
return nil
})
This is the right tool for update types like:
inline_querychosen_inline_resultpollchat_member- other non-command updates
Important:
messagechannel_postcallback_query
stay on the command/payload flow and are not meant to be registered through AddUpdateHandler(...).
The runtime flow
For text commands:
- Telegram update arrives
- bot prepares
MessageContext - bot middleware runs
- matching plugin is found
- plugin middleware runs
- command argument validation runs
- command-specific middleware runs
- command handler runs
- returned error, if any, goes through centralized error handling
For callback payloads, the same idea applies, except the trigger comes from decoded callback data instead of text command parsing.
Middleware placement
You have two main middleware levels:
Plugin middleware
Added with:
plugin.AddMiddleware(...)
Use this for logic shared by most handlers in the plugin.
Command-specific middleware
Added with:
plugin.NewCommand(handler, "name").Use(middleware)
Use this when only one command or payload needs the check.
See Middleware for behavior details.
Good plugin boundaries
Good plugin grouping usually follows one of these patterns:
- by business domain:
billing,admin,profile - by update source:
inline,support,moderation - by ownership: one plugin per subsystem or package
Avoid one giant plugin for the entire bot unless the bot is very small.
Examples
Example: admin command with plugin middleware
admin := laniakea.NewPlugin[*App]("admin")
admin.AddMiddleware(laniakea.NewMiddleware("admin-only", func(ctx *laniakea.MessageContext, app *App) bool {
if !app.IsAdmin(ctx.FromID) {
ctx.Answer("Access denied")
return false
}
return true
}))
admin.AddCommand(admin.NewCommand(func(ctx *laniakea.MessageContext, app *App) error {
ctx.Answer("Banned")
return nil
}, "ban"))
Example: payload handler for inline keyboard callback
plugin.AddPayload(plugin.NewPayload(func(ctx *laniakea.MessageContext, app *App) error {
ctx.AnswerCbQueryText("Accepted")
ctx.EditCallback("Done", nil)
return nil
}, "approve"))
Common mistakes
Putting the slash into command names
Wrong:
plugin.NewCommand(start, "/start")
Right:
plugin.NewCommand(start, "start")
Treating payloads like commands
Payloads are not matched from text messages. They come from button callback data.
Using AddUpdateHandler(...) for command updates
Reserved update types like message, channel_post, and callback_query belong to the normal command/payload pipeline.
Returning errors for normal user-facing branching
Not every branch is an error. For normal usage failures, often the better pattern is:
ctx.Answer("Usage: /ban <id>")
return nil
Return an error when the failure is genuinely exceptional or when you want the centralized error flow.
When to use what
Use:
- commands for slash-prefixed user messages
- payload handlers for inline button callbacks
- update handlers for other Telegram update types
- plugin middleware for shared checks
- command middleware for narrow, local checks
Where to go next
- Read MessageContext next to understand what handlers can do once they are triggered.
- Read Inline-Keyboards-and-Payloads if you are starting to use buttons and callback data.
- Read Middleware for execution-order and async details.
Navigation
Start here
Runtime and Architecture
- Bot-Lifecycle
- Webhook-Runtime
- Middleware
- Runners
- Error-Handling
- Logging
- Update-Routing-Model
- Policies
- Scenes
Interaction and Telegram API
- Inline-Keyboards-and-Payloads
- Auto-Generated-Commands
- Drafts
- Rich-Messages
- Localization
- Rate-Limiting
- tgapi-Overview