A Telegraf application is an object containing an array of middlewares which are composed and executed in a stack-like manner upon request. Is similar to many other middleware systems that you may have encountered such as Koa, Ruby's Rack, Connect.


Middleware is an essential part of any modern framework. It allows you to modify requests and responses as they pass between the Telegram and your bot.

You can imagine middleware as a chain of logic connection your bot to the Telegram request.

Middleware normally takes two parameters (ctx, next), ctx is the context for one Telegram message, next is a function that is invoked to execute the downstream middleware. It returns a Promise with a then function for running code after completion.

const app = new Telegraf(process.env.BOT_TOKEN)

app.use((ctx, next) => {
  const start = new Date()
  return next(ctx).then(() => {
    const ms = new Date() - start
    console.log('Response time %sms', ms)

app.on('text', (ctx) => ctx.reply('Hello World'))

Cascading with async functions

You might need Babel(or node >=v.7.x with harmony flags) for running following example.

app.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date() - start
  console.log('Response time %sms', ms)

Known middleware

Error handling

By default Telegraf will print all errors to stderr and rethrow error.

To perform custom error-handling logic use following snippet:

const app = new Telegraf(process.env.BOT_TOKEN)

app.catch((err) => {
  console.log('Ooops', err)


A Telegraf context encapsulates telegram update.

app.on('sticker', (ctx) => { 
  return ctx.reply('Hey there!')

Context documentation


The recommended namespace to share information between middlewares.

const app = new Telegraf(process.env.BOT_TOKEN)

// Auth middleware
app.use((ctx, next) => {
  ctx.state.role = getUserRole(ctx.message) 
  return next()

app.on('text', (ctx) => {
  return ctx.reply(`Hello ${ctx.state.role}`)


const app = new Telegraf(process.env.BOT_TOKEN)


app.on('text', (ctx) => {
  ctx.session.counter = ctx.session.counter || 0
  return ctx.reply(`Message counter:${ctx.session.counter}`)

Note: For persistent sessions you might use any of telegraf-session-* middleware.

Update types

Supported update types:

  • message
  • edited_message
  • callback_query
  • inline_query
  • shipping_query
  • pre_checkout_query
  • chosen_inline_result
  • channel_post
  • edited_channel_post

Available update sub-types: text, audio, document, photo, sticker, video, voice, contact, location, venue, new_chat_members, left_chat_member, new_chat_title, new_chat_photo, delete_chat_photo, group_chat_created, migrate_to_chat_id, supergroup_chat_created, channel_chat_created, migrate_from_chat_id, pinned_message, game, video_note, invoice, successful_payment.

// Handle message update
bot.on('message', (ctx) =>  {
  return ctx.reply('Hey there!')

// Handle sticker or photo update
bot.on(['sticker', 'photo'], (ctx) =>  {
  return ctx.reply('Cool!')

Related Telegram api docs


const app = new Telegraf(process.env.BOT_TOKEN)

// TLS options
const tlsOptions = {
  key:  fs.readFileSync('server-key.pem'),
  cert: fs.readFileSync('server-cert.pem'),
  ca: [ 
    // This is necessary only if the client uses the self-signed certificate.

// Set telegram webhook
app.telegram.setWebhook('https://server.tld:8443/secret-path', {
  source: fs.readFileSync('server-cert.pem')

// Start https webhook
app.startWebhook('/secret-path', tlsOptions, 8443)

// Http webhook, for nginx/heroku users.
app.startWebhook('/secret-path', null, 5000)

Use webhookCallback() if you want to attach telegraf to existing http server


  .createServer(tlsOptions, app.webhookCallback('/secret-path'))

Connect/Express.js integration

const express = require('express')
const expressApp = express()


expressApp.get('/', (req, res) => {
  res.send('Hello World!')

expressApp.listen(3000, () => {
  console.log('Example app listening on port 3000!')

Working with files

Supported file sources:

  • Existing file_id
  • File path
  • Url
  • Buffer
  • ReadStream

Also you can provide optional name of file as filename.

  bot.on(['sticker', 'photo'], (ctx) =>  {
    // resend existing file by file_id

    // send file
    ctx.answerWithVideo({ source: '/path/to/video.mp4' })

    // send stream
      source: fs.createReadStream('/path/to/video.mp4')
    // send buffer
      source: new Buffer()

    // send url
      url: '',
      filename: 'kitten.jpg'