Skip to main content
The Bot class is the single most important class in grammY. It represents your bot and handles all communication with Telegram.

Creating a Bot

To create a bot, you need a bot token from @BotFather. Once you have your token, instantiate a new Bot:
import { Bot } from 'grammy'

const bot = new Bot('YOUR_BOT_TOKEN')
Never commit your bot token to version control. Use environment variables instead:
const bot = new Bot(process.env.BOT_TOKEN)

Configuration Options

The Bot constructor accepts optional configuration through the BotConfig interface:
config.client
ApiClientOptions
Advanced options for the API client that connects to Telegram’s servers
config.botInfo
UserFromGetMe
Pre-initialize the bot with cached bot information to skip the initial getMe call. Useful for serverless environments where you restart frequently.
config.ContextConstructor
Constructor
Pass a custom context class constructor to use instead of the default Context class

Example with Configuration

const bot = new Bot('YOUR_BOT_TOKEN', {
  client: {
    timeoutSeconds: 60,
  },
  botInfo: {
    // Cached bot info from previous run
    id: 123456789,
    username: 'my_bot',
    // ... other fields
  }
})

Bot API Access

The bot provides full access to the Telegram Bot API through the api property:
// Send a message
await bot.api.sendMessage(chatId, 'Hello from grammY!')

// Get bot information
const me = await bot.api.getMe()
console.log(`Bot username: ${me.username}`)
Inside middleware, prefer using ctx.api instead of bot.api. The context API has the same methods but may include additional features like webhook reply envelopes.

Registering Middleware

The Bot class extends Composer, giving you access to all middleware registration methods:
// Listen for all messages
bot.on('message', ctx => ctx.reply('Got your message!'))

// Listen for text messages only
bot.on('message:text', ctx => {
  console.log('Text:', ctx.message.text)
})

// Listen for specific commands
bot.command('start', ctx => {
  ctx.reply('Welcome! Use /help to see available commands.')
})

// Use general middleware
bot.use(async (ctx, next) => {
  console.log('Update received:', ctx.update.update_id)
  await next()
})
See Middleware and Filter Queries for more details.

Running Your Bot

grammY provides a simple built-in long polling method:
// Start the bot
await bot.start()
The start() method accepts options:
options.limit
number
default:"100"
Number of updates to fetch per request (1-100)
options.timeout
number
default:"30"
Timeout in seconds for long polling
options.allowed_updates
string[]
Array of update types to receive. If not specified, receives all updates except chat_member, message_reaction, and message_reaction_count.
options.drop_pending_updates
boolean
Pass true to drop all pending updates before starting
options.onStart
function
Callback function executed after setup completes, before fetching updates. Receives bot.botInfo as an argument.

Example with Options

await bot.start({
  allowed_updates: ['message', 'callback_query'],
  drop_pending_updates: true,
  onStart: (botInfo) => {
    console.log(`Bot @${botInfo.username} started!`)
  }
})
The built-in bot.start() is designed for small to medium bots. For high-load production bots (>5K messages/hour), use the @grammyjs/runner package for better performance.

Stopping the Bot

To gracefully stop long polling:
await bot.stop()
This will:
  1. Cancel the current getUpdates request
  2. Prevent further getUpdates calls
  3. Confirm the last received update to Telegram

Bot Information

Access information about your bot through the botInfo property:
// Available after initialization
console.log(bot.botInfo.username)
console.log(bot.botInfo.first_name)
console.log(bot.botInfo.id)
The botInfo property is only available after calling await bot.init() or bot.start(). Accessing it before initialization throws an error.

Manual Initialization

If you’re not using bot.start(), initialize the bot manually:
await bot.init()
console.log(`Bot initialized: ${bot.botInfo.username}`)

Error Handling

Set an error handler to catch errors in middleware:
bot.catch((err) => {
  const ctx = err.ctx
  console.error(`Error while handling update ${ctx.update.update_id}:`)
  const e = err.error
  
  if (e instanceof GrammyError) {
    console.error('Error in request:', e.description)
  } else if (e instanceof HttpError) {
    console.error('Could not contact Telegram:', e)
  } else {
    console.error('Unknown error:', e)
  }
})
See Error Handling for comprehensive error handling strategies.

Update Processing

Handling Updates Manually

For webhooks or custom update sources, use handleUpdate():
// In a webhook handler
app.post('/webhook', async (req, res) => {
  await bot.handleUpdate(req.body)
  res.sendStatus(200)
})

Update Flow

When an update arrives:
  1. Bot creates a new Api instance with the bot token
  2. Bot constructs a Context object with the update, API, and bot info
  3. Bot runs the middleware stack with the context
  4. Any errors are caught and passed to the error handler
// This is what happens internally (simplified)
async handleUpdate(update: Update) {
  const api = new Api(this.token, this.clientConfig)
  const ctx = new this.ContextConstructor(update, api, this.me)
  
  try {
    await run(this.middleware(), ctx)
  } catch (err) {
    throw new BotError(err, ctx)
  }
}

Lifecycle Methods

Check if Running

if (bot.isRunning()) {
  console.log('Bot is currently polling')
}

Check if Initialized

if (bot.isInited()) {
  console.log('Bot info available')
}

Default Update Types

By default, grammY requests these update types:
const DEFAULT_UPDATE_TYPES = [
  'message',
  'edited_message',
  'channel_post',
  'edited_channel_post',
  'business_connection',
  'business_message',
  'edited_business_message',
  'deleted_business_messages',
  'inline_query',
  'chosen_inline_result',
  'callback_query',
  'shipping_query',
  'pre_checkout_query',
  'purchased_paid_media',
  'poll',
  'poll_answer',
  'my_chat_member',
  'chat_join_request',
  'chat_boost',
  'removed_chat_boost',
]
If you register listeners for update types not in allowed_updates, grammY will warn you. Always include the update types you need:
bot.on('message_reaction', ctx => { /* ... */ })

await bot.start({
  allowed_updates: ['message', 'message_reaction'] // Include it!
})

Type Safety

The Bot class is generic, allowing custom context types:
import { Bot, Context } from 'grammy'

interface MyContext extends Context {
  session: { count: number }
}

const bot = new Bot<MyContext>('TOKEN')

bot.use((ctx) => {
  // ctx is typed as MyContext
  ctx.session.count++
})
You can also customize the API type:
import { Bot, Context, Api } from 'grammy'

const bot = new Bot<Context, Api>('TOKEN')

Best Practices

Development vs ProductionFor development:
await bot.start()
For production with high load:
import { run } from '@grammyjs/runner'

run(bot)
Graceful Shutdown
process.once('SIGINT', () => bot.stop())
process.once('SIGTERM', () => bot.stop())

await bot.start()
Error Handling is RequiredAlways set an error handler before starting your bot:
bot.catch((err) => {
  console.error('Bot error:', err)
})

await bot.start()
Without an error handler, unhandled errors will crash your bot.

Build docs developers (and LLMs) love