Skip to content

the Telegram client

Telegram is the main entry point. it holds your bot token, owns the api proxy, manages transport (polling or webhook), and dispatches incoming updates

ts
import { Telegram } from 'puregram'

const tg = Telegram.fromToken(process.env.TOKEN!)

tg.onMessage(message => message.send('hi!'))

await tg.startPolling()

constructing the client

Telegram.fromToken(token, options?)

the preferred way. reads sensible defaults and lets you override only what you need:

ts
const tg = Telegram.fromToken(process.env.TOKEN!, {
  retryOnFloodWait: true
})

new Telegram(options)

explicit form — useful when you need full control over every option or when constructing programmatically:

ts
const tg = new Telegram({
  token: process.env.TOKEN!,
  apiBaseUrl: 'https://api.telegram.org/bot',
  apiTimeout: 30_000,
  apiRetryLimit: -1
})

options reference

optiontypedefaultdescription
tokenstringbot token from @BotFather
httpClientHttpClientnative fetchpluggable http backend (see below)
botTelegramUserpre-populate tg.bot, skip the start-time getMe call
allowedUpdatesstring[][] (all)update kinds to receive from telegram
apiBaseUrlstringhttps://api.telegram.org/botapi base url (local bot api, custom proxy)
apiTimeoutnumber30_000per-request timeout in ms
apiWaitnumber3000getUpdates long-poll window in ms
apiRetryLimitnumber-1 (off)max automatic retries per request
apiHeadersRecord<string, string>{}extra headers merged onto every request
useTestDcbooleanfalseroute to the telegram test datacenter
useLocalbooleanfalselocal bot api server mode
retryOnFloodWaitboolean | RetryOnFloodWaitOptionsfalseauto-sleep and retry on 429 responses
swallowDispatchErrorsbooleanfalsesuppress unhandled dispatch errors reaching node's uncaughtException

lifecycle

ts
// long polling — starts the bot, calls getMe (populates tg.bot), then loops getUpdates
await tg.startPolling()

// stop polling gracefully
tg.stopPolling()

// webhook — starts the bot and optionally spins up a built-in node:http listener
await tg.startWebhook({ url: 'https://example.com/bot', port: 3000 })

tg.startPolling() accepts a StartPollingOptions object for fine-tuning the polling loop (allowed updates, timeout, limit). details are in the polling deployment guide

tg.bot is populated after the first startPolling() (or start()) call via a getMe request. if you pass a bot option, the getMe call is skipped and tg.bot is pre-populated immediately

pluggable HttpClient

the default transport uses node's native fetch. to swap it out — for testing, for proxying, or for a custom retry policy — implement the HttpClient interface:

ts
import type { HttpClient, HttpRequestInput } from 'puregram'

const myClient: HttpClient = {
  async request (input: HttpRequestInput) {
    const response = await fetch(input.url, input.init)

    return {
      status: response.status,
      json: () => response.json()
    }
  },

  // optional — only needed if you use tg.download()
  async download (url, init) {
    const response = await fetch(url, init)

    return { status: response.status, body: response.body }
  }
}

const tg = Telegram.fromToken(process.env.TOKEN!, { httpClient: myClient })

the request method is required. download is optional — when omitted, tg.download() falls back to native fetch

token hygiene

never hardcode a bot token. read it from process.env (a .env file + --env-file .env in node 22 is enough) or from a secrets manager. anyone with the token controls the bot — treat it like a password

see also