Skip to main content
Handler methods are the core unit of PolyBot. Each handler is a static or instance method decorated with an update-type attribute. The Roslyn source generator discovers them at compile time and emits a hard-coded BotRouter — no reflection, no runtime discovery.

What is a handler?

A handler is any method that:
  1. Carries exactly one [XxxHandler] attribute from the PolyBot.Attributes namespace.
  2. Returns Task<Result>, ValueTask<Result>, Task, or ValueTask.
Returning plain Task or ValueTask (without Result) stops routing after the handler completes — the generator treats the absence of a result value as an implicit Result.StopRouting.
A handler whose return type does not match any of these four forms is skipped and reported as diagnostic CUR001 (warning). Always use Task<Result>, ValueTask<Result>, Task, or ValueTask. Plain Task/ValueTask stop routing; use Task<Result>/ValueTask<Result> with Result.Continue when you need fall-through behavior.

The HandlerAttribute base class

Every generated [XxxHandler] attribute inherits from HandlerAttribute in the PolyBot.Attributes namespace:
The Priority property follows higher value = earlier execution. A handler with Priority = 0 runs before one with Priority = -1. This is confirmed by DETAILS.md and the test suite, and is the authoritative definition.

Generated update-type attributes

The source generator emits one attribute per UpdateType into the PolyBot.Attributes namespace. These attributes do not exist until after the first build — the Roslyn source generator must run to produce them. You never write these by hand; once the PolyBot.SourceGenerators package is referenced and a build has completed, the full set is available. For Telegram.Bot 22.10 the full set includes:

Priority ordering

Handlers for the same update type are sorted by Priority — higher values run first. When two handlers share the same priority (including the default of 0), they keep their source declaration order.
Use negative priorities (e.g. Priority = -1) for handlers that should run after all the default-priority handlers — this keeps the default 0 as the “normal” slot and reserves positive values for pre-processing steps like logging or auth checks.

Fall-through routing

Result.Continue tells the router to keep going; Result.StopRouting (or its alias Result.Handled()) ends the routing chain for that update. See Routing Result for a full explanation.

Raw update handlers ([UpdateHandler])

[UpdateHandler] marks a handler that receives the raw Telegram.Bot.Types.Update instead of a typed payload — the method must declare an Update parameter (other parameters are resolved as usual). Use it for catch-all / fallback handlers (logging, telemetry, audit trails), cross-update-type filtering, or update types that have no specialized helper attribute yet.
A universal handler (no Types) runs outside the switch (update.Type): with Priority >= 0 it runs before the switch, with negative Priority it runs after it as a fallback for updates no case handled. Result.StopRouting from any of them halts the remaining pipeline, including specialized handlers. Declare Types to restrict the handler to explicit update types. It is then emitted into each matching case branch and sorted among the specialized handlers by Priority (higher first):
AllowedUpdates inference follows the declaration: explicit Types contribute exactly those update types, while a universal handler requests all update types (CUR042 warns about this). Guards that need a typed payload — [Command], [Pattern], state conditions — are not supported on [UpdateHandler] (CUR043); combine it with filter attributes or Result returns instead.

Static vs instance handlers

Handlers may be static or instance methods. The generator treats them differently:
  • Static handlers are called directly with no object allocation.
  • Instance handlers are activated per invocation: the generated router calls GetRequiredService<ContainingType>() on the current IServiceProvider before every invocation. Register the containing class in DI accordingly.
Do not define a type named PolyBotExtensions in the PolyBot namespace. The source generator emits that class name and a collision will cause a compile error.