@puregram/markup
a tagged-template formatter that builds telegram's entities array directly instead of generating a parse_mode string. you compose bold, italic, links, mentions and the rest as values, and the result is a plain { text, entities } payload that goes straight onto the wire
the win: there is no parse_mode, so there's nothing to escape. user input can't break out of formatting because nothing is ever re-parsed back into markdown or html
import { format, bold, italic, code } from '@puregram/markup'
format`hey! this ${bold('message')} is ${italic('formatted')} without ${code('parse_mode')}!`install
yarn add @puregram/markupnpm i -S @puregram/markuppnpm add @puregram/markupmarkup is a .extend plugin and a bag of imports. you call the builders by importing them directly — but to send a Formatted value to telegram you must register the plugin once, so the outgoing entity-unwrapping hook is installed:
import { Telegram } from 'puregram'
import { markup, format, bold, italic, code } from '@puregram/markup'
const tg = Telegram.fromToken(process.env.TOKEN!)
.extend(markup())
tg.onMessage(message =>
message.send(
format`hey! this ${bold('message')} is ${italic('formatted')} without ${code('parse_mode')}!`
)
)
await tg.startPolling().extend(markup()) registers an onBeforeRequest hook that walks every outgoing api call and replaces any Formatted value sitting in a formattable field (text, caption, etc.) with its { text, entities } pair. without it, a Formatted arrives at telegram as [object Object]
WARNING
the builders themselves work without the plugin — bold('hi') returns a Formatted no matter what. but sending one requires .extend(markup()), because that's what teaches the client how to unwrap it. install it once at startup and forget about it
the entry templates — format / formatDedent
format is the tagged template you reach for most. it interpolates builders and strips the leading indentation off the block, so you can write a multi-line message at the indent level of your code:
import { format, bold, italic } from '@puregram/markup'
const f = format`
${bold('build:')} ${italic('passing')}
everything is green
`it strips the first pack of indentation off every line, like stripIndent — extra nested indentation is preserved. formatDedent is the aggressive variant: it strips every leading whitespace run, like stripIndents
both return a Formatted. plain strings and numbers interpolate as literal text; a null / undefined / false interpolation is dropped; another Formatted (or a builder result) splices in with its entities merged
what you get
| surface | what it is |
|---|---|
| builders | bold, italic, link, mentionUser, time, blockquote, join, … — compose formatting as values |
| parsers | html / htmlb / md / markdown — turn an existing html or markdown string into a Formatted |
| codec | Formatted.fromMessage, toHtml() / toMarkdown(), lenient parsing — hydrate, serialize, round-trip |
see also
- builders — the full builder catalog
- parsers —
html/mdand the interpolation model - codec — hydrate from a message, serialize back to source
- formatting text — the raw
parse_modepath, for comparison - plugins & .extend — how
tg.extendworks in general