Skip to main content
The Context class is passed to all middleware functions in grammY. It wraps the update object from Telegram and provides convenient shortcuts for accessing information and calling API methods.

Constructor

new Context(update: Update, api: Api, me: UserFromGetMe)
update
Update
required
The update object from Telegram
api
Api
required
An API instance for calling Bot API methods
me
UserFromGetMe
required
Information about the bot itself
Note: You typically don’t create Context objects manually. grammY creates them for you when processing updates.

Core Properties

update
Update
The complete update object from Telegram containing all information about the incoming update.
bot.on('message', (ctx) => {
  console.log(ctx.update.update_id)
})
api
Api
Full access to the Telegram Bot API. Allows you to call any API method.
ctx.api.sendMessage(chatId, 'Hello!')
Tip: Use context shortcuts like ctx.reply() instead of calling API methods directly when possible.
me
UserFromGetMe
Information about the bot itself as returned by getMe().
console.log(`Bot username: @${ctx.me.username}`)
match
string | RegExpMatchArray | undefined
Used by some middleware to store information about how a string or regular expression was matched.
  • For bot.command(): Contains the text after the command
  • For bot.hears() with regex: Contains the RegExpMatchArray
bot.command('start', (ctx) => {
  console.log('Payload:', ctx.match) // Deep linking payload
})

bot.hears(/\/echo (.+)/, (ctx) => {
  const text = ctx.match[1] // Captured group
  ctx.reply(text)
})

Update Shortcuts

These properties provide quick access to different parts of the update object.
message
Message | undefined
Alias for ctx.update.message
editedMessage
Message | undefined
Alias for ctx.update.edited_message
channelPost
Message | undefined
Alias for ctx.update.channel_post
editedChannelPost
Message | undefined
Alias for ctx.update.edited_channel_post
businessMessage
Message | undefined
Alias for ctx.update.business_message
editedBusinessMessage
Message | undefined
Alias for ctx.update.edited_business_message
callbackQuery
CallbackQuery | undefined
Alias for ctx.update.callback_query
inlineQuery
InlineQuery | undefined
Alias for ctx.update.inline_query
messageReaction
MessageReactionUpdated | undefined
Alias for ctx.update.message_reaction
myChatMember
ChatMemberUpdated | undefined
Alias for ctx.update.my_chat_member
chatMember
ChatMemberUpdated | undefined
Alias for ctx.update.chat_member
And many more for other update types. See the Telegram Bot API documentation for all available update types.

Aggregation Shortcuts

These properties aggregate data from multiple possible sources in the update.
msg
Message | undefined
Get the message object from wherever possible. Checks:
  • message
  • editedMessage
  • channelPost
  • editedChannelPost
  • businessMessage
  • editedBusinessMessage
  • callbackQuery.message
Returns the first non-undefined value.
bot.on('message', (ctx) => {
  console.log(ctx.msg.text) // Works for regular and edited messages
})
chat
Chat | undefined
Get the chat object from wherever possible.
console.log(`Chat ID: ${ctx.chat?.id}`)
console.log(`Chat type: ${ctx.chat?.type}`)
from
User | undefined
Get the user object (message sender) from wherever possible.
console.log(`User: ${ctx.from?.first_name}`)
console.log(`User ID: ${ctx.from?.id}`)
senderChat
Chat | undefined
Get the sender chat object. Alias for ctx.msg?.sender_chat.
msgId
number | undefined
Get the message identifier from wherever possible.
console.log(`Message ID: ${ctx.msgId}`)
chatId
number | undefined
Get the chat identifier from wherever possible.
const chatId = ctx.chatId
if (chatId) {
  await ctx.api.sendMessage(chatId, 'Hello!')
}
inlineMessageId
string | undefined
Get the inline message identifier from callback queries or chosen inline results.
businessConnectionId
string | undefined
Get the business connection identifier from wherever possible.

Utility Methods

entities

Extracts entities from the message text or caption.
ctx.entities(types?: MessageEntity['type'] | MessageEntity['type'][]): Array<MessageEntity & { text: string }>
types
string | string[]
Optional filter for specific entity types (e.g., 'url', 'mention', 'hashtag')
return
Array<MessageEntity & { text: string }>
Array of entities with their extracted text. Returns empty array if no text or entities found.
Example:
bot.on('message:entities', (ctx) => {
  // Get all entities
  const allEntities = ctx.entities()
  console.log('All entities:', allEntities)
  
  // Get only URLs
  const urls = ctx.entities('url')
  urls.forEach(entity => {
    console.log('URL found:', entity.text)
  })
  
  // Get URLs and mentions
  const mixed = ctx.entities(['url', 'mention'])
})

reactions

Analyzes message reaction updates to determine which reactions were added, removed, or kept.
ctx.reactions(): ReactionInfo
return
ReactionInfo
Object containing information about the reaction update:
Example:
bot.on('message_reaction', (ctx) => {
  const reactions = ctx.reactions()
  
  if (reactions.emojiAdded.includes('👍')) {
    console.log('User added thumbs up!')
  }
  
  if (reactions.emojiRemoved.length > 0) {
    console.log('Removed reactions:', reactions.emojiRemoved)
  }
  
  console.log('Current reactions:', reactions.emoji)
})

Context Probing Methods

These methods check if the context matches certain conditions.

has

Checks if the context matches a filter query.
ctx.has(filter: FilterQuery | FilterQuery[]): boolean
filter
FilterQuery | FilterQuery[]
required
The filter query to check
Example:
bot.on('message', (ctx) => {
  if (ctx.has(':text')) {
    console.log('Message has text')
  }
  
  if (ctx.has('message:entities:url')) {
    console.log('Message contains URLs')
  }
})

hasText

Checks if the context contains specific text or matches a regex.
ctx.hasText(trigger: string | RegExp | Array<string | RegExp>): boolean

hasCommand

Checks if the context contains a specific command.
ctx.hasCommand(command: string | string[]): boolean

hasReaction

Checks if the context contains a specific reaction.
ctx.hasReaction(reaction: ReactionType | ReactionType[]): boolean

hasChatType

Checks if the context belongs to a specific chat type.
ctx.hasChatType(chatType: Chat['type'] | Chat['type'][]): boolean
Example:
bot.on('message', (ctx) => {
  if (ctx.hasChatType('private')) {
    console.log('Private chat message')
  }
})

Static Methods

Context.has

Provides static methods to generate predicate functions for context probing.
const hasText = Context.has.filterQuery(':text')
if (hasText(ctx)) {
  console.log('Has text:', ctx.msg.text)
}

const hasCommand = Context.has.command('start')
if (hasCommand(ctx)) {
  console.log('Is start command')
}

API Shortcut Methods

The Context class provides convenient shortcuts for common API operations.

reply

Sends a text message to the same chat.
await ctx.reply(text: string, other?: SendMessageOptions, signal?: AbortSignal): Promise<Message>
text
string
required
Text of the message to send (1-4096 characters)
other
object
Optional parameters like parse_mode, reply_markup, etc.
Example:
bot.command('start', async (ctx) => {
  await ctx.reply('Welcome to the bot!')
  
  await ctx.reply('**Bold text**', {
    parse_mode: 'Markdown'
  })
})

replyWithPhoto

Sends a photo to the same chat.
await ctx.replyWithPhoto(photo: InputFile | string, other?: SendPhotoOptions): Promise<Message>
Example:
import { InputFile } from 'grammy'

bot.command('photo', async (ctx) => {
  // From file
  await ctx.replyWithPhoto(new InputFile('/path/to/photo.jpg'))
  
  // From URL
  await ctx.replyWithPhoto('https://example.com/photo.jpg')
  
  // From file_id
  await ctx.replyWithPhoto('AgACAgIAAxkBAAI...')
  
  // With caption
  await ctx.replyWithPhoto(photo, {
    caption: 'Look at this photo!'
  })
})

replyWithAudio

Sends an audio file.
await ctx.replyWithAudio(audio: InputFile | string, other?: SendAudioOptions): Promise<Message>

replyWithDocument

Sends a document file.
await ctx.replyWithDocument(document: InputFile | string, other?: SendDocumentOptions): Promise<Message>

replyWithVideo

Sends a video file.
await ctx.replyWithVideo(video: InputFile | string, other?: SendVideoOptions): Promise<Message>

replyWithAnimation

Sends an animation (GIF or video without sound).
await ctx.replyWithAnimation(animation: InputFile | string, other?: SendAnimationOptions): Promise<Message>

replyWithVoice

Sends a voice message.
await ctx.replyWithVoice(voice: InputFile | string, other?: SendVoiceOptions): Promise<Message>

replyWithVideoNote

Sends a video note (round video message).
await ctx.replyWithVideoNote(videoNote: InputFile | string, other?: SendVideoNoteOptions): Promise<Message>

replyWithMediaGroup

Sends a group of photos, videos, documents or audios as an album.
await ctx.replyWithMediaGroup(media: InputMedia[], other?: SendMediaGroupOptions): Promise<Message[]>

replyWithLocation

Sends a location point.
await ctx.replyWithLocation(latitude: number, longitude: number, other?: SendLocationOptions): Promise<Message>
Example:
bot.command('location', (ctx) => {
  ctx.replyWithLocation(51.5074, -0.1278, {
    // London coordinates
  })
})

replyWithVenue

Sends information about a venue.
await ctx.replyWithVenue(
  latitude: number,
  longitude: number,
  title: string,
  address: string,
  other?: SendVenueOptions
): Promise<Message>

replyWithContact

Sends a phone contact.
await ctx.replyWithContact(
  phoneNumber: string,
  firstName: string,
  other?: SendContactOptions
): Promise<Message>

replyWithPoll

Sends a native poll.
await ctx.replyWithPoll(
  question: string,
  options: string[],
  other?: SendPollOptions
): Promise<Message>
Example:
bot.command('poll', (ctx) => {
  ctx.replyWithPoll(
    'What is your favorite color?',
    ['Red', 'Blue', 'Green', 'Yellow'],
    {
      is_anonymous: false
    }
  )
})

replyWithDice

Sends an animated emoji with a random value.
await ctx.replyWithDice(emoji?: '🎲' | '🎯' | '🏀' | '⚽' | '🎳' | '🎰'): Promise<Message>
Example:
bot.command('roll', (ctx) => {
  ctx.replyWithDice('🎲') // Rolls a die
})

forwardMessage

Forwards a message to another chat.
await ctx.forwardMessage(chatId: number | string, other?: ForwardMessageOptions): Promise<Message>

copyMessage

Copies a message to another chat (without the forward header).
await ctx.copyMessage(chatId: number | string, other?: CopyMessageOptions): Promise<MessageId>

deleteMessage

Deletes the current message.
await ctx.deleteMessage(): Promise<true>
Example:
bot.command('delete', async (ctx) => {
  await ctx.reply('This message will be deleted in 3 seconds...')
  await new Promise(resolve => setTimeout(resolve, 3000))
  await ctx.deleteMessage()
})

answerCallbackQuery

Answers a callback query from an inline button.
await ctx.answerCallbackQuery(options?: AnswerCallbackQueryOptions): Promise<true>
Example:
bot.callbackQuery('button_id', async (ctx) => {
  await ctx.answerCallbackQuery('Button clicked!')
  
  // Or show an alert
  await ctx.answerCallbackQuery({
    text: 'This is an alert!',
    show_alert: true
  })
})

editMessageText

Edits the text of a message.
await ctx.editMessageText(text: string, other?: EditMessageTextOptions): Promise<Message | true>
Example:
bot.callbackQuery('edit', async (ctx) => {
  await ctx.editMessageText('Message text has been edited!')
})

editMessageReplyMarkup

Edits the reply markup (keyboard) of a message.
await ctx.editMessageReplyMarkup(replyMarkup?: InlineKeyboardMarkup): Promise<Message | true>

Complete Example

import { Bot } from 'grammy'

const bot = new Bot('YOUR_BOT_TOKEN')

// Access update information
bot.on('message', (ctx) => {
  console.log('Update ID:', ctx.update.update_id)
  console.log('Chat ID:', ctx.chatId)
  console.log('User:', ctx.from?.first_name)
  console.log('Message ID:', ctx.msgId)
})

// Use context probing
bot.on('message', (ctx) => {
  if (ctx.has(':text')) {
    console.log('Text:', ctx.msg.text)
  }
  
  if (ctx.hasChatType('private')) {
    console.log('Private chat')
  }
})

// Extract entities
bot.on('message:entities', (ctx) => {
  const urls = ctx.entities('url')
  if (urls.length > 0) {
    ctx.reply(`Found ${urls.length} URL(s)!`)
  }
})

// Use API shortcuts
bot.command('start', async (ctx) => {
  await ctx.reply('Welcome!')
  await ctx.replyWithPhoto('https://example.com/logo.png', {
    caption: 'Our logo'
  })
})

// Handle reactions
bot.on('message_reaction', (ctx) => {
  const r = ctx.reactions()
  if (r.emojiAdded.includes('❤️')) {
    console.log('User sent love!')
  }
})

bot.start()

See Also

Build docs developers (and LLMs) love