[MessageHandler] method with [Command] to restrict it to messages whose first entity is a bot command. The source generator emits all entity-checking, argument-parsing, and @botname-suffix verification code so you never write routing boilerplate by hand.
Aliases can be declared positionally: [Command("start", "begin")] is shorthand for [Command(Aliases = ["start", "begin"])] — both forms are equivalent and can be combined.
The [Command] attribute
Properties at a glance
How command matching works
WhenPrefix is '/' (the default), PolyBot checks that the message’s first entity is of type BotCommand. If the command text includes an @botname suffix — such as /start@MyBot — the suffix is verified against PolyBotOptions.BotUsername, which is populated automatically by PolyBotClient.RunPollingAsync() via GetMe. A mismatched suffix falls through to the next handler; other handlers (like an echo fallback) continue routing normally.
'/' prefixes, PolyBot matches by string.StartsWith (ordinal-ignore-case) against Message.Text directly — no entity required. A space (' ') or null character ('\0') prefix matches text starting with the alias alone.
Guest messages
Guest updates arrive with the bot’s mention first —@PolyBot /start — and Telegram marks that mention with a Mention entity at offset 0. On a [GuestMessageHandler], [Command] uses that entity to find where the command begins and matches from there, so the example below handles only /start:
@BotName /cmd without a Mention entity does not match. The @botsuffix check on the command entity itself (/start@MyBot) still applies. Non-'/' prefixes work the same way: the remainder after the mention entity is matched as if it were the whole text. Imperative code can use the same rule via CommandHelper.MatchGuestCommand.
CommandScope values
The Scope property maps to Telegram’s BotCommandScope and controls which chats see the command in the BotFather menu.
Argument extraction
Arguments are the whitespace-separated tokens that follow the command word in the message text. Three attributes let you bind handler parameters to those tokens instead of resolving them from the DI container.[Arg] — single token
Binds one whitespace-separated token by position. The token is converted using TryParse (invariant culture for numeric types). Set IsOptional = true to yield the parameter’s default value instead of a parse error when the token is absent.
[Arg] parameter types must be the argument type, Nullable<T>, or object. Any other type produces warning CUR005 and falls back to DI injection.[Rest] — remainder string
Binds everything that follows the positionally consumed [Arg] tokens as a single trimmed string. At most one [Rest] parameter is allowed per handler; more produce warning CUR006.
[Parse] — regex match
Binds a string parameter to the first match of a regular expression scanned (not anchored) against the remaining text. If there are capturing groups, Groups[1].Value is bound; otherwise the whole match is used. A non-match throws CommandArgsParseException.
[Parse] must be applied to a string parameter (CUR009) and cannot be declared after a [Rest] parameter on the same handler (CUR008).BotFather auto-sync
Commands with aDescription property are automatically collected into PolyBotBotFatherSync and synchronized with BotFather on startup. The generator groups commands by (Scope, LanguageCode) and sends one SetMyCommands call per unique pair.
Description still route normally, but are skipped during synchronization — the Bot API rejects empty command descriptions, so pushing one would throw. CUR021 (warning) reminds you to either set a description or mark the command IsHidden = true.'/'-prefixed commands are synchronized; commands with a custom Prefix are always hidden from BotFather regardless of IsHidden.^[a-z0-9_]{1,32}$; violations are reported as compile-time error CUR020.
Diagnostics reference
CUR002 – [Command] on a non-Message handler
CUR002 – [Command] on a non-Message handler
[Command] is placed on a handler whose update payload is not a Message. The command filter is ignored and the handler routes unconditionally.CUR003 – [Arg]/[Rest]/[Parse] without [Command]
CUR003 – [Arg]/[Rest]/[Parse] without [Command]
[Command]. The parameter is treated as a normal DI dependency.CUR004 – Non-constant Prefix
CUR004 – Non-constant Prefix
Prefix property is not a compile-time constant. Falls back to '/'.CUR005 – [Arg] type mismatch
CUR005 – [Arg] type mismatch
[Arg] parameter type is not the argument type, Nullable<T>, or object. The parameter is resolved from DI instead.CUR006 – Multiple [Rest] parameters
CUR006 – Multiple [Rest] parameters
[Rest] parameter is declared on a handler. Extra parameters beyond the first are treated as ordinary DI parameters.CUR007 – [Rest] on a non-string parameter
CUR007 – [Rest] on a non-string parameter
[Rest] is applied to a parameter whose type is not string. The parameter falls back to DI.CUR008 – [Parse] after [Rest]
CUR008 – [Parse] after [Rest]
[Parse] parameter is declared after a [Rest] parameter. The [Parse] parameter falls back to DI since [Rest] absorbs all remaining text.CUR009 – [Parse] on a non-string parameter
CUR009 – [Parse] on a non-string parameter
[Parse] is applied to a parameter whose type is not string. Falls back to DI.CUR020 – Invalid BotFather alias format
CUR020 – Invalid BotFather alias format
^[a-z0-9_]{1,32}$ and cannot be registered with BotFather.CUR021 – Missing Description
CUR021 – Missing Description
'/'-prefixed command without IsHidden = true has no Description. The command is excluded from BotFather sync.CUR022 – Description over 256 characters
CUR022 – Description over 256 characters
Description string exceeds BotFather’s 256-character limit.CUR023 – Duplicate alias in (scope, language) bucket
CUR023 – Duplicate alias in (scope, language) bucket
(Scope, LanguageCode) pair. The second registration is discarded.CUR052 – Alias starts with the command prefix
CUR052 – Alias starts with the command prefix
Prefix (e.g. Aliases = ["/start"] with the default / prefix), so the matcher looks for //start and the command can never match. Drop the leading prefix from the alias.CUR046 – GuestMessage handler calls SendMessage
CUR046 – GuestMessage handler calls SendMessage
[GuestMessageHandler] calls SendMessage. Guest messages must be answered with AnswerGuestQuery using the message’s GuestQueryId.CUR047 – GuestMessage handler can answer the guest query more than once
CUR047 – GuestMessage handler can answer the guest query more than once
AnswerGuestQuery executes twice on one update (sequential calls, two calls in one loop body, or one before and one inside a loop). Mutually exclusive if/else branches with one call each are not flagged.