telegram-bot-kit is a lightweight, TypeScript-first SDK for building Telegram bots in Node.js and Next.js. Designed for serverless deployments (e.g., Vercel), it provides a simple, event-driven wrapper around the Telegram Bot API, enabling full control over messaging, media, interactions, and more.
- Send text messages with formatting, keyboards, and markup
- Edit, delete, and replace messages
- Handle callback queries and answer buttons
- Send photos, audio, voice, animations, stickers, polls, venues, and contacts
- Upload files via URLs or local buffers/streams
- Download user-uploaded files
- Inline keyboards and reply keyboards
- Force reply and one-time keyboards
- Polls and venue sharing
- Forward messages
- Pin/unpin messages (single or all)
- Delete messages
- Full event-driven update processing (messages, callbacks, polls, payments, etc.)
- Inline query and chosen inline result support
- Error handling with descriptive API exceptions
- TypeScript interfaces for all Telegram structures
- Webhook-first design (no polling needed)
- ESM compatible (Node.js >=18)
- Lightweight with zero external runtime dependencies (uses only
cross-fetch
for HTTP)
npm install telegram-bot-kit
-
Create a Bot: Talk to @BotFather and get your token.
-
Basic Usage:
import { TelegramBot } from 'telegram-bot-kit';
const bot = new TelegramBot('YOUR_BOT_TOKEN');
- Handle Messages (Webhook Example):
import { createWebhookHandler } from 'telegram-bot-kit';
// In your server (e.g., Next.js API route)
const handler = createWebhookHandler(bot, 'YOUR_WEBHOOK_SECRET');
bot.on('message', (msg) => {
if (msg.text === '/start') {
bot.sendMessage(msg.chat.id, 'Hello! Welcome to telegram-bot-kit.');
}
});
- Deploy: Set your webhook and deploy to Vercel!
// Simple text
await bot.sendMessage(chatId, 'Hello World!');
// With inline keyboard
const keyboard = {
inline_keyboard: [
[{ text: 'Yes', callback_data: 'yes' }, { text: 'No', callback_data: 'no' }]
]
};
await bot.sendMessage(chatId, 'Choose:', { reply_markup: keyboard });
// With reply keyboard
const replyMarkup = {
keyboard: [[{ text: 'Button 1' }, { text: 'Button 2' }]],
one_time_keyboard: true
};
await bot.sendMessage(chatId, 'Choose an option:', { reply_markup: replyMarkup });
// Send photo from URL
await bot.sendPhoto(chatId, 'https://example.com/photo.jpg', 'Caption here');
// Upload audio file
const audioBuffer = fs.readFileSync('audio.mp3');
await bot.sendAudio(chatId, audioBuffer, 'Audio title', { duration: 120 });
// Send poll
await bot.sendPoll(chatId, 'What\'s your favorite?', ['Option 1', 'Option 2'], {
is_anonymous: false, allows_multiple_answers: true
});
// Handle callbacks (button presses)
bot.on('callback_query', (query) => {
bot.answerCallbackQuery(query.id, 'Button clicked!');
bot.sendMessage(query.message.chat.id, `You chose: ${query.data}`);
});
// Handle location requests
bot.on('message', (msg) => {
if (msg.location) {
const { latitude, longitude } = msg.location;
bot.sendVenue(msg.chat.id, latitude, longitude, 'Your Location', 'Address here');
}
});
// Inline queries (when users @mention your bot)
bot.on('inline_query', (query) => {
// Respond to user searches
bot.answerInlineQuery(query.id, [{
type: 'article',
id: '1',
title: 'Sample Result',
input_message_content: { message_text: 'Inline result!' }
}]);
});
// Send initial message
const msg = await bot.sendMessage(chatId, 'Initial text');
const messageId = msg.result.message_id;
// Edit text
await bot.editMessageText(chatId, messageId, 'Updated text', { parse_mode: 'Markdown' });
// Delete after delay
setTimeout(() => bot.deleteMessage(chatId, messageId), 5000);
// Get file info
const fileResponse = await bot.getFile('file_id_from_message');
const fileObj = fileResponse.result;
// Get download URL
const downloadUrl = bot.getFileDownloadUrl(fileObj);
// Download using fetch
const response = await fetch(downloadUrl);
const buffer = await response.arrayBuffer();
// Save or process buffer
In api/telegram-webhook.ts
:
import { TelegramBot, createWebhookHandler } from 'telegram-bot-kit';
const bot = new TelegramBot();
const handler = createWebhookHandler(bot, process.env.WEBHOOK_SECRET);
export default handler;
Set webhook in your bot setup:
import { setWebhook } from 'telegram-bot-kit';
await setWebhook(token, { url: 'https://your-domain.com/api/telegram-webhook' });
Send a text message with optional formatting, keyboards, etc.
// Simple message
await bot.sendMessage(chatId, 'Hello World!');
// With Markdown formatting
await bot.sendMessage(chatId, 'Hello **world**!', { parse_mode: 'Markdown' });
// With inline keyboard
await bot.sendMessage(chatId, 'Choose an option:', {
reply_markup: {
inline_keyboard: [
[{ text: 'Yes', callback_data: 'yes_action' }, { text: 'No', callback_data: 'no_action' }]
]
}
});
// With reply keyboard
await bot.sendMessage(chatId, 'Select:', {
reply_markup: {
keyboard: [[{ text: 'Option 1' }, { text: 'Option 2' }]],
one_time_keyboard: true
}
});
// Disable notifications
await bot.sendMessage(chatId, 'Silent message', { disable_notification: true });
Edit an existing text message.
const msg = await bot.sendMessage(chatId, 'Initial text');
const messageId = msg.result.message_id;
await bot.editMessageText(chatId, messageId, 'Updated text', { parse_mode: 'HTML' });
Edit a message's caption.
await bot.editMessageCaption(chatId, messageId, 'New caption', { parse_mode: 'Markdown' });
Delete a message.
await bot.deleteMessage(chatId, messageId);
Answer a callback query from an inline keyboard.
bot.on('callback_query', async (query) => {
await bot.answerCallbackQuery(query.id, {
text: 'Processing your choice...',
show_alert: false
});
});
Edit a message's inline keyboard markup.
await bot.editMessageReplyMarkup(chatId, messageId, {
inline_keyboard: [[{ text: 'New Button', callback_data: 'new_action' }]]
});
Send a message with inline keyboard (convenience method).
await bot.sendInlineButtons(chatId, 'Choose action:', [
[{ text: 'Yes', callback_data: 'yes' }, { text: 'No', callback_data: 'no' }],
[{ text: 'Maybe', callback_data: 'maybe' }]
]);
Send a message with reply keyboard (convenience method).
await bot.sendButtons(chatId, 'Choose an option:', [
[{ text: 'Option 1' }, { text: 'Option 2' }],
[{ text: 'Cancel' }]
], true, false); // resize_keyboard: true, one_time_keyboard: false
Request user's phone number with a reply keyboard.
await bot.requestPhoneNumber(chatId, 'Please share your contact to continue.');
bot.on('contact', (contact) => {
console.log('Phone:', contact.contact?.phone_number);
});
Request user's location with a reply keyboard.
await bot.requestLocation(chatId, 'Where are you?');
bot.on('location', (location) => {
console.log('Coords:', location.location?.latitude, location.location?.longitude);
});
Hide/remove the custom keyboard for a user.
await bot.hideKeyboard(chatId, 'Keyboard hidden!');
Send a photo (URL, Buffer, or file path).
// From URL
await bot.sendPhoto(chatId, 'https://example.com/photo.jpg', 'Photo caption');
// From file (Node.js)
const photoBuffer = require('fs').readFileSync('photo.jpg');
await bot.sendPhoto(chatId, photoBuffer, 'Uploaded photo', { filename: 'my_photo.jpg' });
Send an audio file.
const audioBuffer = require('fs').readFileSync('song.mp3');
await bot.sendAudio(chatId, audioBuffer, 'Song title', {
duration: 180,
title: 'Track Name',
performer: 'Artist'
});
Send a voice message.
const voiceBuffer = require('fs').readFileSync('voice.ogg');
await bot.sendVoice(chatId, voiceBuffer, 'Voice note');
Send an animation/GIF.
await bot.sendAnimation(chatId, 'https://example.com/animation.gif', 'Cute animation!');
Send a sticker.
const stickerBuffer = require('fs').readFileSync('sticker.webp');
await bot.sendSticker(chatId, stickerBuffer);
Send a document/file.
const fileBuffer = require('fs').readFileSync('document.pdf');
await bot.sendDocument(chatId, fileBuffer, 'Important document', {
filename: 'report.pdf'
});
Send a video file.
const videoBuffer = require('fs').readFileSync('video.mp4');
await bot.sendVideo(chatId, videoBuffer, 'Video description');
Send a poll.
await bot.sendPoll(chatId, 'What\'s your favorite color?', ['Red', 'Blue', 'Green'], {
is_anonymous: false,
allows_multiple_answers: true,
correct_option_id: 1, // For quiz mode
explanation: 'Blue is the most popular!'
});
Send a venue/location.
await bot.sendVenue(chatId, 40.7128, -74.0060, 'New York City', 'Manhattan, NY');
Send a contact.
await bot.sendContact(chatId, '+1234567890', 'John Doe', {
last_name: 'Smith',
vcard: 'BEGIN:VCARD\\nVERSION:3.0\\nFN:John Doe\\nTEL:+1234567890\\nEND:VCARD'
});
Forward a message from one chat to another.
await bot.forwardMessage(myChatId, originalChatId, messageId, {
disable_notification: true
});
Pin a message in a chat.
await bot.pinChatMessage(chatId, messageId, {
disable_notification: false
});
Unpin a specific message.
await bot.unpinChatMessage(chatId, messageId);
Unpin all pinned messages in a chat.
await bot.unpinAllChatMessages(chatId);
Get file information and download path.
const fileResponse = await bot.getFile('file_id_from_message');
console.log('Download URL available at:', bot.getFileDownloadUrl(fileResponse.result));
Generate a download URL for a file object.
const file = { file_path: 'path/to/file' };
const url = bot.getFileDownloadUrl(file);
// Use with fetch or display/download
Alias for editMessageText (shorter name).
await bot.replaceMessage(chatId, messageId, 'New content');
Process a Telegram update and emit appropriate events.
// In webhook handler
export async function POST(req) {
const update = await req.json();
bot.handleUpdate(update);
return new Response('OK');
}
Set or delete a webhook for your bot.
import { setWebhook } from 'telegram-bot-kit';
await setWebhook('your_bot_token', {
url: 'https://yourapp.com/webhook',
secret_token: 'secret_for_webhooks',
max_connections: 100,
allowed_updates: ['message', 'callback_query']
});
// To remove webhook (delete)
await setWebhook('your_bot_token', { url: '' });
Create a request handler for processing Telegram webhooks.
import { createWebhookHandler } from 'telegram-bot-kit';
const webhookHandler = createWebhookHandler(bot, 'your_secret_token');
// Express.js example
app.post('/telegram-webhook', webhookHandler);
// Next.js API route
export default webhookHandler;
Process a Telegram update and emit appropriate events.
// In webhook handler
export async function POST(req) {
const update = await req.json();
bot.handleUpdate(update);
return new Response('OK');
}
Set or delete a webhook for your bot.
import { setWebhook } from 'telegram-bot-kit';
await setWebhook('your_bot_token', {
url: 'https://yourapp.com/webhook',
secret_token: 'secret_for_webhooks',
max_connections: 100,
allowed_updates: ['message', 'callback_query']
});
// To remove webhook (delete)
await setWebhook('your_bot_token', { url: '' });
Create a request handler for processing Telegram webhooks.
import { createWebhookHandler } from 'telegram-bot-kit';
const webhookHandler = createWebhookHandler(bot, 'your_secret_token');
// Express.js example
app.post('/telegram-webhook', webhookHandler);
// Next.js API route
export default webhookHandler;
All events receive the corresponding Telegram object as the first parameter.
(Triggered by user actions like sending messages; use with sendMessage
method)
bot.on('message', (msg) => {
if (msg.text === '/start') {
bot.sendMessage(msg.chat.id, 'Welcome!');
}
});
bot.on('edited_message', (msg) => {
bot.sendMessage(msg.chat.id, 'Message edited!');
});
bot.on('channel_post', (msg) => {
console.log('New channel post:', msg.text);
});
bot.on('edited_channel_post', (msg) => {
console.log('Channel post edited:', msg.text);
});
bot.on('callback_query', async (query) => {
await bot.answerCallbackQuery(query.id, 'Button clicked!');
await bot.sendMessage(query.message.chat.id, `You chose: ${query.data}`);
});
bot.on('inline_query', async (query) => {
const results = [
{ type: 'article', id: '1', title: 'Result 1', input_message_content: { message_text: 'Content 1' } }
];
await bot.answerInlineQuery(query.id, results);
});
bot.on('chosen_inline_result', (result) => {
console.log('User chose:', result.result_id);
});
bot.on('contact', (contact) => {
console.log('Phone:', contact.contact?.phone_number, 'Name:', contact.contact?.first_name);
});
bot.on('location', (location) => {
const { latitude, longitude } = location.location!;
bot.sendMessage(location.chat.id, `Your coords: ${latitude}, ${longitude}`);
});
bot.on('shipping_query', async (query) => {
// Answer with shipping options
});
bot.on('pre_checkout_query', async (query) => {
// Validate payment (ok: true/false)
});
bot.on('poll', (poll) => {
console.log('Poll updated:', poll.question, 'Total votes:', poll.total_voter_count);
});
bot.on('poll_answer', (answer) => {
console.log('User voted options:', answer.option_ids);
});
bot.on('my_chat_member', (update) => {
console.log('Bot membership changed:', update.new_chat_member.status);
});
bot.on('chat_member', (update) => {
console.log(update.new_chat_member.user.first_name, 'is now', update.new_chat_member.status);
});
bot.on('chat_join_request', (request) => {
// Approve or decline join request
console.log(request.from.first_name, 'wants to join', request.chat.title);
});
Most methods accept an options
object for parameters like:
parse_mode
: 'Markdown' | 'HTML' | 'MarkdownV2'disable_notification
: booleanreply_markup
: Inline or reply keyboard- And more per Telegram API docs.
Full type safety with interfaces for:
Message
,User
,Chat
,Update
,CallbackQuery
, etc.- Import from
telegram-bot-kit
for direct use.
Methods throw Error
with Telegram's API description on failure. Wrap calls in try-catch:
try {
await bot.sendMessage(chatId, 'Test');
} catch (error) {
console.error('API Error:', error.message);
}
TELEGRAM_BOT_TOKEN
: Bot token (can also pass directly to constructor)
- Fixed Next.js build failures by replacing
node-fetch
withcross-fetch
for isomorphic HTTP support, enabling client-side bundling compatibility.
TELEGRAM_BOT_TOKEN
: Bot token (can also pass directly to constructor)
MIT
Fork and submit PRs. For major changes, open an issue first.
Built for simplicity, power, and serverless compatibility!