Documentation Index
Fetch the complete documentation index at: https://mintlify.com/grammyjs/grammY/llms.txt
Use this file to discover all available pages before exploring further.
grammY provides three main error types to help you handle different failure scenarios in your bot.
BotError<C>
Thrown when middleware throws an error. Wraps the original error and provides access to the context.
class BotError<C extends Context = Context> extends Error {
constructor(
public readonly error: unknown,
public readonly ctx: C
)
}
Properties
error: The original error that was thrown by your middleware
ctx: The context object being processed when the error occurred
message: A descriptive error message
name: Always "BotError"
stack: Stack trace from the original error (if available)
When it occurs
BotError is thrown whenever an error occurs during middleware execution:
bot.on('message', (ctx) => {
throw new Error('Something went wrong!') // Will be wrapped in BotError
})
bot.on('message', async (ctx) => {
await riskyOperation() // If this throws, it becomes BotError
})
Handling BotError
Use bot.catch() to install an error handler:
import { BotError } from 'grammy'
bot.catch((err: BotError) => {
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)
}
})
Accessing context from errors
The ctx property lets you access update information when an error occurs:
bot.catch(async (err: BotError) => {
const ctx = err.ctx
// Notify user about the error
if (ctx.chat) {
await ctx.reply('Sorry, an error occurred while processing your request.')
}
// Log detailed information
console.error('Error details:', {
updateId: ctx.update.update_id,
userId: ctx.from?.id,
chatId: ctx.chat?.id,
error: err.error
})
})
Example: Retry on error
bot.catch(async (err: BotError) => {
if (err.error instanceof GrammyError) {
if (err.error.error_code === 429) {
// Rate limited - retry after delay
const retryAfter = err.error.parameters.retry_after ?? 30
console.log(`Rate limited. Retrying after ${retryAfter}s`)
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000))
// Re-handle the update
await bot.handleUpdate(err.ctx.update)
}
}
})
GrammyError
Thrown when the Telegram Bot API returns an error response.
class GrammyError extends Error implements ApiError {
public readonly ok: false = false
public readonly error_code: number
public readonly description: string
public readonly parameters: ResponseParameters
constructor(
message: string,
err: ApiError,
public readonly method: string,
public readonly payload: Record<string, unknown>
)
}
Properties
error_code: Telegram’s error code (e.g., 400, 404, 429)
description: Human-readable error description from Telegram
parameters: Additional parameters (e.g., retry_after for rate limits)
method: The API method that was called (e.g., "sendMessage")
payload: The parameters passed to the API method
ok: Always false
Common Error Codes
| Code | Description |
|---|
| 400 | Bad Request - invalid parameters |
| 401 | Unauthorized - invalid bot token |
| 403 | Forbidden - no permission to perform action |
| 404 | Not Found - resource doesn’t exist |
| 409 | Conflict - bot is already running elsewhere |
| 429 | Too Many Requests - rate limited |
| 500 | Internal Server Error - Telegram server issue |
When it occurs
GrammyError is thrown when Telegram’s API returns an error:
try {
await ctx.reply('Hello!')
} catch (err) {
if (err instanceof GrammyError) {
console.error('Telegram API error:', err.description)
}
}
Example: Handling specific errors
import { GrammyError } from 'grammy'
try {
await bot.api.sendMessage(chatId, text)
} catch (err) {
if (err instanceof GrammyError) {
switch (err.error_code) {
case 400:
console.error('Bad request:', err.description)
break
case 403:
console.error('Bot was blocked by user')
// Remove user from database
break
case 429:
const retryAfter = err.parameters.retry_after
console.error(`Rate limited. Retry after ${retryAfter}s`)
break
default:
console.error('Telegram error:', err)
}
}
}
Example: Handling message deletion
try {
await ctx.deleteMessage()
} catch (err) {
if (err instanceof GrammyError) {
if (err.description.includes('message to delete not found')) {
console.log('Message already deleted')
} else if (err.description.includes("message can't be deleted")) {
console.log('Message is too old to delete')
} else {
throw err // Re-throw unknown errors
}
}
}
Example: Handling bot blocking
bot.on('message', async (ctx) => {
try {
await ctx.reply('Hello!')
} catch (err) {
if (err instanceof GrammyError) {
if (err.error_code === 403) {
console.log(`User ${ctx.from.id} blocked the bot`)
// Update user status in database
await db.users.update(ctx.from.id, { blocked: true })
}
}
}
})
ResponseParameters
Some errors include additional parameters:
interface ResponseParameters {
migrate_to_chat_id?: number // Group migrated to supergroup
retry_after?: number // Seconds to wait before retry
}
Example:
try {
await ctx.reply('Message')
} catch (err) {
if (err instanceof GrammyError) {
if (err.parameters.migrate_to_chat_id) {
const newChatId = err.parameters.migrate_to_chat_id
console.log(`Chat migrated to ${newChatId}`)
// Update chat ID in database
}
}
}
HttpError
Thrown when the HTTP request to Telegram’s servers fails.
class HttpError extends Error {
constructor(
message: string,
public readonly error: unknown
)
}
Properties
error: The underlying network error
message: Descriptive error message
name: Always "HttpError"
When it occurs
HttpError is thrown when:
- Network connection fails
- DNS resolution fails
- Request times out
- Connection is aborted
- API transformer function throws
import { HttpError } from 'grammy'
try {
await bot.api.sendMessage(chatId, 'Hello')
} catch (err) {
if (err instanceof HttpError) {
console.error('Network error:', err.error)
// Retry logic here
}
}
Example: Retry on network failure
import { HttpError } from 'grammy'
async function sendWithRetry(chatId: number, text: string, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await bot.api.sendMessage(chatId, text)
} catch (err) {
if (err instanceof HttpError) {
console.log(`Network error. Retry ${i + 1}/${maxRetries}`)
if (i === maxRetries - 1) throw err // Last retry failed
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)))
} else {
throw err // Not a network error
}
}
}
}
Error Handling Patterns
Global Error Handler
Handle all errors in one place:
import { BotError, GrammyError, HttpError } from 'grammy'
bot.catch((err: BotError) => {
const e = err.error
if (e instanceof GrammyError) {
console.error('API Error:', e.description)
if (e.error_code === 401) {
console.error('Invalid bot token!')
process.exit(1)
}
} else if (e instanceof HttpError) {
console.error('Network error:', e)
} else {
console.error('Unknown error:', e)
}
})
Local Error Handling
Handle errors in specific middleware:
bot.command('start', async (ctx) => {
try {
await ctx.reply('Welcome!')
} catch (err) {
console.error('Failed to send welcome message:', err)
// Don't crash the bot, just log it
}
})
Error Boundaries
Create protected middleware sections:
bot.errorBoundary(
(err) => {
console.error('Error in admin commands:', err)
},
adminCommands.middleware()
)
Ignore Specific Errors
Silently handle expected errors:
bot.on('message', async (ctx) => {
try {
await ctx.deleteMessage()
} catch (err) {
if (err instanceof GrammyError) {
// Ignore "message not found" errors
if (!err.description.includes('message to delete not found')) {
throw err
}
} else {
throw err
}
}
})
Custom Error Classes
Create application-specific errors:
class UserNotFoundError extends Error {
constructor(public userId: number) {
super(`User ${userId} not found`)
this.name = 'UserNotFoundError'
}
}
bot.on('message', async (ctx) => {
const user = await db.findUser(ctx.from.id)
if (!user) {
throw new UserNotFoundError(ctx.from.id)
}
// ...
})
bot.catch((err: BotError) => {
if (err.error instanceof UserNotFoundError) {
console.log('New user:', err.error.userId)
// Create user in database
}
})
Error Logging
Log errors with context:
bot.catch(async (err: BotError) => {
const errorLog = {
timestamp: new Date().toISOString(),
updateId: err.ctx.update.update_id,
userId: err.ctx.from?.id,
chatId: err.ctx.chat?.id,
errorType: err.error?.constructor.name,
errorMessage: err.error instanceof Error ? err.error.message : String(err.error),
stack: err.stack
}
console.error(JSON.stringify(errorLog, null, 2))
// Send to external logging service
await logger.error(errorLog)
})
Best Practices
-
Always set an error handler
bot.catch((err) => {
console.error('Bot error:', err)
})
-
Handle specific errors explicitly
if (err instanceof GrammyError && err.error_code === 403) {
// User blocked the bot
}
-
Don’t leak sensitive information
// ❌ Bad - might expose token in logs
console.error(err)
// ✅ Good - sanitize error
console.error(err.description)
-
Implement retry logic for transient errors
if (err instanceof HttpError || err.error_code >= 500) {
// Retry
}
-
Fail gracefully
try {
await ctx.reply('Result')
} catch {
await ctx.reply('Sorry, something went wrong')
}
Proper error handling makes your bot robust and reliable!