Bots are special Telegram accounts designed to handle messages automatically. Users can interact with bots by sending them command messages in private or group chats. These accounts serve as an interface for code running somewhere on your server.
Telegraf is a library that makes it simple for you to develop your own Telegram bots using JavaScript or TypeScript.
- Full Telegram Bot API 5.0 support
- Telegram Payment Platform
- HTML5 Games
- Inline mode
- Lightweight
- Firebase/Glitch/Heroku/AWS λ/Whatever ready
http/https/fastify/Connect.js/express.jscompatible webhooks- Easy to extend
TypeScripttypings
const { Telegraf } = require('telegraf')
const bot = new Telegraf(process.env.BOT_TOKEN)
bot.start((ctx) => ctx.reply('Welcome'))
bot.help((ctx) => ctx.reply('Send me a sticker'))
bot.on('sticker', (ctx) => ctx.reply('👍'))
bot.hears('hi', (ctx) => ctx.reply('Hey there'))
bot.launch()
// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'))
process.once('SIGTERM', () => bot.stop('SIGTERM'))const { Telegraf } = require('telegraf')
const bot = new Telegraf(process.env.BOT_TOKEN)
bot.command('oldschool', (ctx) => ctx.reply('Hello'))
bot.command('hipster', Telegraf.reply('λ'))
bot.launch()
// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'))
process.once('SIGTERM', () => bot.stop('SIGTERM'))For additional bot examples see examples folder.
- Getting started
- Telegram groups (sorted by number of members):
- GitHub Discussions
- Dependent repositories
To use the Telegram Bot API, you first have to get a bot account by chatting with BotFather.
BotFather will give you a token, something like 123456789:AbCdfGhIJKlmNoQQRsTUVwxyZ.
$ npm install telegraf
or
$ yarn add telegraf
or
$ pnpm add telegraf
A Telegraf bot 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 Express, 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 the two parameters: ctx and next.
ctx is the context for one Telegram update. It contains mainly two things:
- the update object, containing for example the incoming message and the respective chat, and
- a number of useful methods for reacting to the update, such as replying to the message or answering a callback query.
See the context section below for a detailed overview.
next is a function that is invoked to execute the downstream middleware.
It returns a Promise with a function then for running code after completion.
Here is a simple example for how to use middleware to track the response time, using async and await to deal with the Promise.
const bot = new Telegraf(process.env.BOT_TOKEN)
bot.use(async (ctx, next) => {
const start = new Date()
await next()
const ms = new Date() - start
console.log('Response time: %sms', ms)
})
bot.on('text', (ctx) => ctx.reply('Hello World'))
bot.launch()
// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'))
process.once('SIGTERM', () => bot.stop('SIGTERM'))Note how the function next is used to invoke the subsequent layers of the middleware stack, performing the actual processing of the update (in this case, replying with “Hello World”).
Middleware is an extremely flexible concept that can be used for a myriad of things, including these:
- storing data per chat, per user, you name it
- allowing access to old messages (by storing them)
- making internationalization available
- rate limiting
- tracking response times (see above)
- much more
All important kinds of middleware have already been implemented, and the community keeps on adding more.
Just install a package via npm, add it to your bot and you're ready to go.
Here is a list of
- Internationalization—simplifies selecting the right translation to use when responding to a user.
- Redis powered session—store session data using Redis.
- Local powered session (via lowdb)—store session data in a local file.
- Rate-limiting—apply rate limitting to chats or users.
- Bottleneck powered throttling—apply throttling to both incoming updates and outgoing API calls.
- Menus via inline keyboards—simplify creating interfaces based on menus.
- Stateless Questions—create stateless questions to Telegram users working in privacy mode.
- Natural language processing via wit.ai
- Natural language processing via recast.ai
- Multivariate and A/B testing—add experiments to see how different versions of a feature are used.
- Powerfull bot stats via Mixpanel
- statsd integration
- and more...
By default Telegraf will print all errors to stderr and rethrow error.
To perform custom error-handling logic, use following snippet:
const bot = new Telegraf(process.env.BOT_TOKEN)
bot.catch((err, ctx) => {
console.log(`Ooops, encountered an error for ${ctx.updateType}`, err)
})
bot.start((ctx) => {
throw new Error('Example error')
})
bot.launch()
// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'))
process.once('SIGTERM', () => bot.stop('SIGTERM'))A Telegraf Context encapsulates telegram update.
One Context is created for each incoming Update.
The recommended way to extend bot context:
const bot = new Telegraf(process.env.BOT_TOKEN)
bot.context.db = {
getScores: () => { return 42 }
}
bot.on('text', (ctx) => {
const scores = ctx.db.getScores(ctx.message.from.username)
return ctx.reply(`${ctx.message.from.username}: ${scores}`)
})
bot.launch()
// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'))
process.once('SIGTERM', () => bot.stop('SIGTERM'))If you're using TypeScript, have a look at the section below about usage with TypeScript. (You need to extend the type of the context.)
const bot = new Telegraf(process.env.BOT_TOKEN)
bot.command('quit', (ctx) => {
// Explicit usage
ctx.telegram.leaveChat(ctx.message.chat.id)
// Using context shortcut
ctx.leaveChat()
})
bot.on('text', (ctx) => {
// Explicit usage
ctx.telegram.sendMessage(ctx.message.chat.id, `Hello ${ctx.state.role}`)
// Using context shortcut
ctx.reply(`Hello ${ctx.state.role}`)
})
bot.on('callback_query', (ctx) => {
// Explicit usage
ctx.telegram.answerCbQuery(ctx.callbackQuery.id)
// Using context shortcut
ctx.answerCbQuery()
})
bot.on('inline_query', (ctx) => {
const result = []
// Explicit usage
ctx.telegram.answerInlineQuery(ctx.inlineQuery.id, result)
// Using context shortcut
ctx.answerInlineQuery(result)
})
bot.launch()
// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'))
process.once('SIGTERM', () => bot.stop('SIGTERM'))The recommended namespace to share information between middlewares.
const bot = new Telegraf(process.env.BOT_TOKEN)
// Naive authorization middleware
bot.use((ctx, next) => {
ctx.state.role = getUserRole(ctx.message)
return next()
})
bot.on('text', (ctx) => {
return ctx.reply(`Hello ${ctx.state.role}`)
})
bot.launch()
// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'))
process.once('SIGTERM', () => bot.stop('SIGTERM'))Sessions are used to store data per user or per chat (or per whatever if you want, this is the session key).
Think of a session as an object that can hold any kind of information you provide. This could be the ID of the last message of the bot, or simply a counter about how many photos a user already sent to the bot.
You can use session middleware to add sessions support to your bot. This will do the heavy lifting for you. Using session middleware will result in a sequence like this:
- A new update comes in.
- The session middleware loads the current session data for the respective chat/user/whatever.
- The session middleware makes that session data available on the context object
ctx. - Your middleware stack is run, all of your code can do its work.
- The session middleware takes back control and checks how you altered the session data on the
ctxobject. - The session middleware write the session back to your storage, i.e. a file, a database, an in-memory storage, or even a cloud storage solution.
Here is a simple example of how the built-in session middleware of Telegraf can be used to count photos.
const { session } = require('telegraf')
const bot = new Telegraf(process.env.BOT_TOKEN)
bot.use(session())
bot.on('photo', (ctx) => {
ctx.session ??= { counter: 0 }
ctx.session.counter++
return ctx.reply(`Photo counter: ${ctx.session.counter}`)
})
bot.launch()
// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'))
process.once('SIGTERM', () => bot.stop('SIGTERM'))The default session key is .
If either ${ctx.from.id}:${ctx.chat.id}ctx.from or ctx.chat is undefined, default session key and thus ctx.session are also undefined.
You can customize the session key resolver function by passing in the options argument:
const { session } = require('telegraf')
const bot = new Telegraf(process.env.BOT_TOKEN)
bot.use(session({
makeKey: (ctx) => ctx.from?.id // only store data per user, but across chats
}))
bot.on('photo', (ctx) => {
ctx.session ??= { counter: 0 }
ctx.session.counter++
return ctx.reply(`Photo counter: ${ctx.session.counter}`)
})
bot.launch()
// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'))
process.once('SIGTERM', () => bot.stop('SIGTERM'))Tip: To use same session in private chat with bot and in inline mode, use following session key resolver:
{
makeKey: (ctx) => {
if (ctx.from && ctx.chat) {
return `${ctx.from.id}:${ctx.chat.id}`
} else if (ctx.from && ctx.inlineQuery) {
return `${ctx.from.id}:${ctx.from.id}`
}
return undefined
}
}However, in the above example, the session middleware just stores the counters in-memory. This means that all counters will be lost when the process is terminated. If you want to store data across restarts, or share it among workers, you need to use persistent sessions.
There are already a lot of packages that make this a breeze.
You can simply add npm install one and to your bot to support exactly the type of storage you want.
Alternatively, telegraf also allows you to easily integrate your own persistence without any other package.
The session function can take a storage in the options object.
A storage must have three methods: one for loading, one for storing, and one for deleting a session.
This works as follows:
const { session } = require('telegraf')
// may also return `Promise`s (or use `async` functions)!
const storage = {
getItem(key) { /* load a session for `key` ... */ },
setItem(key, value) { /* save a session for `key` ... */ },
deleteItem(key) { /* delete a session for `key` ... */ }
}
const bot = new Telegraf(process.env.BOT_TOKEN)
bot.use(session({ storage }))
bot.on('photo', (ctx) => {
ctx.session.counter = ctx.session.counter || 0
ctx.session.counter++
return ctx.reply(`Photo counter: ${ctx.session.counter}`)
})
bot.launch()
// Enable graceful stop
process.once('SIGINT', () => bot.stop('SIGINT'))
process.once('SIGTERM', () => bot.stop('SIGTERM'))// Handle message update
bot.on('message', (ctx) => {
return ctx.reply('Hello')
})
// Handle sticker or photo update
bot.on(['sticker', 'photo'], (ctx) => {
console.log(ctx.message)
return ctx.reply('Cool!')
})require('dotenv')
const bot = 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 a self-signed certificate.
fs.readFileSync('client-cert.pem')
]
}
// Set telegram webhook
// The second argument is necessary only if the client uses a self-signed
// certificate. Including it for a verified certificate may cause things to break.
bot.telegram.setWebhook('https://server.tld:8443/secret-path', {
source: 'server-cert.pem'
})
// Start https webhook
bot.startWebhook('/secret-path', tlsOptions, 8443)
// Http webhook, for nginx/heroku users.
bot.startWebhook('/secret-path', null, 5000)Use webhookCallback() if you want to attach Telegraf to an existing http server.
require('http')
.createServer(bot.webhookCallback('/secret-path'))
.listen(3000)
require('https')
.createServer(tlsOptions, bot.webhookCallback('/secret-path'))
.listen(8443)- AWS Lambda example integration
expressexample integrationfastifyexample integrationkoaexample integration
Supported file sources:
Existing file_idFile pathUrlBufferReadStream
Also, you can provide an optional name of a file as filename when you send the file.
bot.on('message', (ctx) => {
// resend existing file by file_id
ctx.replyWithSticker('123123jkbhj6b')
// send file
ctx.replyWithVideo({ source: '/path/to/video.mp4' })
// send stream
ctx.replyWithVideo({
source: fs.createReadStream('/path/to/video.mp4')
})
// send buffer
ctx.replyWithVoice({
source: Buffer.alloc()
})
// send url via Telegram server
ctx.replyWithPhoto('https://picsum.photos/200/300/')
// pipe url content
ctx.replyWithPhoto({
url: 'https://picsum.photos/200/300/?random',
filename: 'kitten.jpg'
})
})Telegraf Modules is higher level abstraction for writing modular Telegram bots.
A module is simply a .js file that exports Telegraf middleware:
module.exports = (ctx) => ctx.reply('Hello from Telegraf Module!')const Composer = require('telegraf/composer')
module.exports = Composer.mount(
'sticker',
(ctx) => ctx.reply('Wow, sticker')
)To run modules, you can use telegraf module runner, it allows you to start Telegraf module easily from the command line.
npm install telegraf -g
telegraf [opts] <bot-file>
-t Bot token [$BOT_TOKEN]
-d Webhook domain
-H Webhook host [0.0.0.0]
-p Webhook port [$PORT or 3000]
-l Enable logs
-h Show this help message
Create module with name bot.js and following content:
const Composer = require('telegraf/composer')
const PhotoURL = 'https://picsum.photos/200/300/?random'
const bot = new Composer()
bot.start((ctx) => ctx.reply('Hello there!'))
bot.help((ctx) => ctx.reply('Help message'))
bot.command('photo', (ctx) => ctx.replyWithPhoto({ url: PhotoURL }))
module.exports = botthen run it:
telegraf -t "bot token" bot.js
Telegraf is written in TypeScript and therefore ships with declaration files for the entire library.
Moreover, it includes types for the complete Telegram API via the typegram package.
While most types of Telegraf's API surface are self-explanatory, there's some notable things to keep in mind.
Recap from the above section about Middleware that ctx is the context object that holds information about the incoming update, as well as a number of convenience functions such as ctx.reply.
The exact shape of ctx can vary based on the installed middleware.
Some custom middleware might register properties on the context object that Telegraf is not aware of.
Consequently, you can change the type of ctx to fit your needs in order for you to have proper TypeScript types for your data.
This is done through Generics:
import { Context, Telegraf } from "telegraf";
// Define your own context type
interface MyContext extends Context {
myProp?: string
myOtherProp?: number
}
// Create your bot and tell it about your context type
const bot = new Telegraf<MyContext>('SECRET TOKEN')
// Register middleware and launch your bot as usual
bot.use((ctx, next) => {
// Yay, `myProp` is now available here as `string | undefined`!
ctx.myProp = ctx.chat?.first_name?.toUpperCase()
return next()
})
// ...If you are using session middleware, you need to define your session property on your custom context object. This could look like this:
import { Context, Telegraf } from 'telegraf'
import { session } from 'telegraf'
interface SessionData {
lastMessageId?: number
photoCount?: number
// ... more session data go here
}
// Define your own context type
interface MyContext extends Context {
session?: SessionData
// ... more props go here
}
// Create your bot and tell it about your context type
const bot = new Telegraf<MyContext>('SECRET TOKEN')
// Make session data available
bot.use(session())
// Register middleware and launch your bot as usual
bot.use((ctx, next) => {
// Yay, `session` is now available here as `SessionData`!
if (ctx.message !== undefined)
ctx.session.lastMessageId = ctx.message.message_id
return next()
})
bot.on('photo', (ctx, next) => {
ctx.session.photoCount = 1 + (ctx.session.photoCount ?? 0)
return next()
})
// ...Coming soon!