Skip to main content
When a user taps an inline keyboard button, Telegram delivers a CallbackQuery whose Data field is a short string you chose when building the keyboard. Rather than parsing that string yourself, PolyBot lets you declare a route template with [Pattern] and extract typed captures directly into handler parameters — the same [Arg] ergonomics used for command arguments.

The [Pattern] attribute

[Pattern] declares a segment template over CallbackQuery.Data. Segments are separated by Separator (default ':'). A non-matching update falls through to the next handler without throwing.

Template syntax

Literal segments

Plain text matches the segment value exactly (ordinal comparison).

Named placeholders — {name}

Captures one segment into the [Arg]-annotated parameter with the same name. When the parameter type is string, the segment value is bound directly. For numeric and enum types, the generator inlines a TryParse check; a failing parse causes the handler to fall through.
Name inference follows the same rules as command [Arg]: when the [Arg] attribute is placed without an explicit name, the parameter name is used.

Type constraints — {name:type}

An inline type constraint (e.g. {id:int}) is checked against the parameter’s declared type. A mismatch between the constraint and the parameter type is a compile-time error (CUR030).

Wildcard tail — {*name}

A trailing {*name} placeholder captures everything after the preceding segment separator into a string parameter. The captured value may be empty. Only one wildcard is allowed, and it must be the last segment.

Type conversion rules

A failed parse causes the pattern guard to emit false and the handler falls through to the next candidate — no exception is thrown.

Imperative matching in inline await loops

[Pattern] covers routing into handlers, but inline conversations that await callbacks in a loop (via IUpdateAwaiter) used to fall back to manual CallbackSegments probing. The template-based CallbackPatternMatcher.Match overloads are the imperative twin of [Pattern]: same template syntax, same conversion rules, but the captures bind positionally to generic type arguments:
  • Overloads exist for zero (literal-only) through five captures; a char overload of each takes an explicit separator, the rest default to ':'.
  • Generic arguments correspond to {name} placeholders in template order — placeholder names are documentation only at this point.
  • A {*name} wildcard tail binds to a string argument and accepts any remaining text, exactly like [Pattern].
  • Template problems (unbalanced braces, wildcard not in tail position, placeholder/out-argument count mismatch, wildcard bound to a non-string) throw ArgumentException immediately — they are coding errors, not data errors. Data that simply doesn’t match returns false.
  • A capture type with no accessible static TryParse throws InvalidOperationException on first use — the runtime counterpart of diagnostic CUR028.

Zero-allocation matching

The CallbackPatternMatcher / CallbackSegments implementation used by the generated router is zero-allocation per update: segment bounds are recomputed by scanning the callback_data string rather than splitting it into an array. This means pattern routing adds no heap pressure on the hot path. The template-based Match<T…> overloads above are meant for inline await loops, not the hot path; they parse the template and allocate capture substrings per call.

The 64-byte callback_data limit

Telegram enforces a 64-byte limit on callback_data. PolyBot enforces this at compile time:
  • CUR024 (error) — the literal skeleton of the template alone exceeds 64 bytes and can never fit regardless of captured values.
  • CUR025 (error) — the literal skeleton plus the minimum non-empty sizes for each placeholder already exceeds 64 bytes.
Keep templates short and prefer integer IDs over long strings in callback data to stay well within the 64-byte limit.

Complete examples

Diagnostics reference

Severity: ErrorThe literal portion of the template (all segments and separators, excluding placeholder values) is already over 64 bytes. The template can never produce valid callback_data.
Severity: ErrorEven assuming the minimum non-empty value (1 byte) for each placeholder, the template exceeds 64 bytes.
Severity: ErrorA {name} placeholder in the template has no corresponding [Arg]-annotated parameter on the handler method.
Severity: ErrorA handler parameter carries [Arg] but its name does not appear as a placeholder in the template.
Severity: ErrorA captured parameter’s type does not have an accessible TryParse method (and is not string or an enum). The generator cannot emit a type-safe extraction.
Severity: Error[Pattern] is applied to a handler that is not a [CallbackQueryHandler]. The attribute has no effect outside callback-query routing.
Severity: ErrorThe inline type constraint in {name:type} does not match the declared type of the corresponding [Arg] parameter.
Severity: ErrorThe template string is structurally invalid (e.g. unclosed braces, wildcard not in tail position).