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.
[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
charoverload 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 astringargument 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) throwArgumentExceptionimmediately — they are coding errors, not data errors. Data that simply doesn’t match returnsfalse. - A capture type with no accessible static
TryParsethrowsInvalidOperationExceptionon first use — the runtime counterpart of diagnosticCUR028.
Zero-allocation matching
TheCallbackPatternMatcher / 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.
Complete examples
- Simple pattern
- Type-constrained pattern
- Wildcard pattern
- Decimal capture
Diagnostics reference
CUR024 – Template literal skeleton exceeds 64 bytes
CUR024 – Template literal skeleton exceeds 64 bytes
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.CUR025 – Template with minimum placeholder sizes exceeds 64 bytes
CUR025 – Template with minimum placeholder sizes exceeds 64 bytes
Severity: ErrorEven assuming the minimum non-empty value (1 byte) for each placeholder, the template exceeds 64 bytes.
CUR026 – Placeholder without matching [Arg] parameter
CUR026 – Placeholder without matching [Arg] parameter
Severity: ErrorA
{name} placeholder in the template has no corresponding [Arg]-annotated parameter on the handler method.CUR027 – [Arg] parameter not captured by template
CUR027 – [Arg] parameter not captured by template
Severity: ErrorA handler parameter carries
[Arg] but its name does not appear as a placeholder in the template.CUR028 – Parameter type lacks TryParse
CUR028 – Parameter type lacks TryParse
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.CUR029 – [Pattern] on a non-callback handler
CUR029 – [Pattern] on a non-callback handler
Severity: Error
[Pattern] is applied to a handler that is not a [CallbackQueryHandler]. The attribute has no effect outside callback-query routing.CUR030 – Constraint/type mismatch
CUR030 – Constraint/type mismatch
Severity: ErrorThe inline type constraint in
{name:type} does not match the declared type of the corresponding [Arg] parameter.CUR031 – Malformed template
CUR031 – Malformed template
Severity: ErrorThe template string is structurally invalid (e.g. unclosed braces, wildcard not in tail position).