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.
The Composer class is the heart of grammY’s middleware system. It provides methods for composing and organizing middleware into a middleware stack. The Bot class extends Composer, so all methods are available on your bot instance.
Overview
A composer allows you to:
- Register middleware that processes updates
- Filter updates based on various criteria
- Organize middleware into reusable components
- Control the flow of update processing
Constructor
new Composer(...middleware: Middleware<C>[])
Optional middleware functions to initialize the composer with. If no middleware is provided, the composer will pass all updates through unchanged.
Example:
import { Composer } from 'grammy'
// Empty composer
const composer = new Composer()
// With initial middleware
const composer = new Composer(
(ctx, next) => {
console.log('First middleware')
return next()
},
(ctx, next) => {
console.log('Second middleware')
return next()
}
)
Basic Methods
use
Registers middleware that receives all updates.
composer.use(...middleware: Middleware<C>[]): Composer<C>
The middleware function(s) to register
A new composer instance that can be further extended
Example:
composer.use((ctx, next) => {
console.log('Processing update:', ctx.update.update_id)
return next()
})
// Chain multiple use calls
composer
.use(middleware1)
.use(middleware2)
.use(middleware3)
Registers middleware for specific update types using filter queries.
composer.on<Q extends FilterQuery>(
filter: Q | Q[],
...middleware: Middleware<Filter<C, Q>>[]
): Composer<Filter<C, Q>>
filter
FilterQuery | FilterQuery[]
required
The filter query or array of queries to match
middleware
Middleware<Filter<C, Q>>[]
required
Middleware to execute when the filter matches
Example:
// Single filter
composer.on('message:text', (ctx) => {
console.log('Text message:', ctx.message.text)
})
// Multiple filters (OR logic)
composer.on(['message:photo', 'message:video'], (ctx) => {
console.log('Media received')
})
// Chaining filters (AND logic)
composer
.on('::url') // Has URL entity
.on(':forward_origin') // Is forwarded
.use((ctx) => {
console.log('Forwarded message with URL')
})
hears
Registers middleware for messages matching text or regular expressions.
composer.hears(
trigger: string | RegExp | Array<string | RegExp>,
...middleware: HearsMiddleware<C>[]
): Composer<HearsContext<C>>
trigger
string | RegExp | Array<string | RegExp>
required
The text or regex to match against message text or captions
Example:
// Exact match
composer.hears('hello', (ctx) => {
ctx.reply('Hello to you too!')
})
// Regex match
composer.hears(/\/echo (.+)/, (ctx) => {
const text = ctx.match[1]
ctx.reply(text)
})
// Multiple triggers
composer.hears(['hi', 'hey', 'hello'], (ctx) => {
ctx.reply('Greetings!')
})
command
Registers middleware for specific bot commands.
composer.command(
command: string | string[],
...middleware: CommandMiddleware<C>[]
): Composer<CommandContext<C>>
command
string | string[]
required
The command(s) to match (without the leading /)
Example:
composer.command('start', (ctx) => {
ctx.reply('Welcome!')
console.log('Start payload:', ctx.match)
})
composer.command(['help', 'info'], (ctx) => {
ctx.reply('Help information...')
})
reaction
Registers middleware for message reaction updates.
composer.reaction(
reaction: ReactionType | ReactionType[],
...middleware: ReactionMiddleware<C>[]
): Composer<ReactionContext<C>>
reaction
ReactionTypeEmoji['emoji'] | ReactionType | Array
required
The reaction emoji or type to match
Example:
composer.reaction('👍', (ctx) => {
console.log('Thumbs up received!')
})
composer.reaction(['❤️', '🔥'], (ctx) => {
console.log('Popular reaction!')
})
chatType
Registers middleware for specific chat types.
composer.chatType<T extends Chat['type']>(
chatType: T | T[],
...middleware: Middleware<ChatTypeContext<C, T>>[]
): Composer<ChatTypeContext<C, T>>
chatType
'private' | 'group' | 'supergroup' | 'channel' | Array
required
The chat type(s) to match
Example:
// Private chats only
composer.chatType('private', (ctx) => {
console.log('Private message')
})
// Groups and supergroups
composer.chatType(['group', 'supergroup'], (ctx) => {
console.log('Group message')
})
callbackQuery
Registers middleware for callback queries.
composer.callbackQuery(
trigger: string | RegExp | Array<string | RegExp>,
...middleware: CallbackQueryMiddleware<C>[]
): Composer<CallbackQueryContext<C>>
Example:
composer.callbackQuery('btn_confirm', async (ctx) => {
await ctx.answerCallbackQuery('Confirmed!')
await ctx.editMessageText('You confirmed the action.')
})
composer.callbackQuery(/^page_(\d+)$/, async (ctx) => {
const page = ctx.match[1]
await ctx.editMessageText(`Showing page ${page}`)
})
inlineQuery
Registers middleware for inline queries.
composer.inlineQuery(
trigger: string | RegExp | Array<string | RegExp>,
...middleware: InlineQueryMiddleware<C>[]
): Composer<InlineQueryContext<C>>
Example:
composer.inlineQuery('search', async (ctx) => {
const results = [/* ... */]
await ctx.answerInlineQuery(results)
})
Advanced Methods
filter
Registers middleware behind a custom filter function.
// With type predicate
composer.filter<D extends C>(
predicate: (ctx: C) => ctx is D,
...middleware: Middleware<D>[]
): Composer<D>
// With boolean predicate
composer.filter(
predicate: (ctx: C) => boolean | Promise<boolean>,
...middleware: Middleware<C>[]
): Composer<C>
predicate
(ctx: C) => boolean | Promise<boolean>
required
Function that determines whether to execute the middleware
Example:
// Simple filter
composer.filter(
(ctx) => ctx.from?.is_premium === true,
(ctx) => {
console.log('Premium user!')
}
)
// Type predicate
function hasText(ctx): ctx is Context & { message: { text: string } } {
return ctx.message?.text !== undefined
}
composer.filter(hasText, (ctx) => {
// ctx.message.text is guaranteed to exist
console.log(ctx.message.text)
})
drop
Registers middleware that runs only if the predicate returns false.
composer.drop(
predicate: (ctx: C) => boolean | Promise<boolean>,
...middleware: Middleware<C>[]
): Composer<C>
Example:
// Skip bot messages
composer.drop(
(ctx) => ctx.from?.is_bot === true,
(ctx) => {
console.log('Human user message')
}
)
fork
Runs middleware concurrently with the main middleware stack.
composer.fork(...middleware: Middleware<C>[]): Composer<C>
Example:
bot.use((ctx, next) => {
console.log('First middleware')
return next()
})
bot.fork((ctx) => {
// Runs concurrently with next middleware
console.log('Forked middleware')
})
bot.use((ctx) => {
console.log('This runs in parallel with fork')
})
lazy
Executes dynamically generated middleware.
composer.lazy(
middlewareFactory: (ctx: C) => Middleware<C> | Middleware<C>[] | Promise<Middleware<C> | Middleware<C>[]>
): Composer<C>
middlewareFactory
(ctx: C) => Middleware<C> | Middleware<C>[]
required
Factory function that creates middleware for each context. Can be async. Return an empty array to skip middleware.
Example:
composer.lazy((ctx) => {
// Generate middleware based on context
if (ctx.from?.language_code === 'en') {
return (ctx) => ctx.reply('Hello!')
} else {
return (ctx) => ctx.reply('Hola!')
}
})
// Return multiple middleware
composer.lazy(async (ctx) => {
const handlers = await loadHandlersFromDatabase()
return handlers
})
route
Branches between different middleware based on a routing function.
composer.route<R extends Record<PropertyKey, Middleware<C>>>(
router: (ctx: C) => keyof R | undefined | Promise<keyof R | undefined>,
routeHandlers: R,
fallback?: Middleware<C>
): Composer<C>
router
(ctx: C) => keyof R | undefined
required
Function that returns the key of the middleware to execute
routeHandlers
Record<string, Middleware<C>>
required
Object mapping route keys to middleware
Optional fallback middleware if no route matches
Example:
const routes = {
text: (ctx) => console.log('Text:', ctx.msg.text),
photo: (ctx) => console.log('Photo received'),
video: (ctx) => console.log('Video received')
}
composer.route(
(ctx) => {
if (ctx.msg?.text) return 'text'
if (ctx.msg?.photo) return 'photo'
if (ctx.msg?.video) return 'video'
return undefined
},
routes,
(ctx) => console.log('Unknown message type')
)
branch
Branches between two middleware paths based on a predicate.
composer.branch(
predicate: (ctx: C) => boolean | Promise<boolean>,
trueMiddleware: Middleware<C> | Middleware<C>[],
falseMiddleware: Middleware<C> | Middleware<C>[]
): Composer<C>
predicate
(ctx: C) => boolean | Promise<boolean>
required
Condition to test
trueMiddleware
Middleware<C> | Middleware<C>[]
required
Middleware to run if predicate returns true
falseMiddleware
Middleware<C> | Middleware<C>[]
required
Middleware to run if predicate returns false
Example:
composer.branch(
(ctx) => ctx.from?.is_premium === true,
(ctx) => ctx.reply('Welcome, premium user!'),
(ctx) => ctx.reply('Welcome, regular user!')
)
errorBoundary
Installs an error boundary to catch errors in middleware.
composer.errorBoundary(
errorHandler: (error: BotError<C>, next: NextFunction) => unknown | Promise<unknown>,
...middleware: Middleware<C>[]
): Composer<C>
errorHandler
(error: BotError<C>, next: NextFunction) => unknown
required
Function to handle errors. Call next() to continue to downstream middleware after handling the error.
Middleware to protect with the error boundary
Example:
function errorHandler(err: BotError, next: NextFunction) {
console.error('Error caught:', err.error)
// Optionally continue processing
return next()
}
const protected = composer.errorBoundary(
errorHandler,
middleware1,
middleware2
)
protected.on('message', middleware3) // Also protected
Helper Methods
gameQuery
Registers middleware for game queries.
composer.gameQuery(
trigger: string | RegExp | Array<string | RegExp>,
...middleware: GameQueryMiddleware<C>[]
): Composer<GameQueryContext<C>>
chosenInlineResult
Registers middleware for chosen inline results.
composer.chosenInlineResult(
resultId: string | RegExp | Array<string | RegExp>,
...middleware: ChosenInlineResultMiddleware<C>[]
): Composer<ChosenInlineResultContext<C>>
preCheckoutQuery
Registers middleware for pre-checkout queries.
composer.preCheckoutQuery(
trigger: string | RegExp | Array<string | RegExp>,
...middleware: PreCheckoutQueryMiddleware<C>[]
): Composer<PreCheckoutQueryContext<C>>
shippingQuery
Registers middleware for shipping queries.
composer.shippingQuery(
trigger: string | RegExp | Array<string | RegExp>,
...middleware: ShippingQueryMiddleware<C>[]
): Composer<ShippingQueryContext<C>>
Middleware Function
Get the composed middleware function.
composer.middleware(): MiddlewareFn<C>
The composed middleware function
Example:
const composer = new Composer()
composer.use((ctx) => console.log('Hello'))
const middleware = composer.middleware()
bot.use(middleware) // Install on bot
Complete Example
import { Bot, Composer } from 'grammy'
const bot = new Bot('YOUR_BOT_TOKEN')
// Create a composer for admin commands
const adminComposer = new Composer()
adminComposer.filter(
(ctx) => ctx.from?.id === ADMIN_ID,
(ctx, next) => {
console.log('Admin command')
return next()
}
)
adminComposer.command('stats', (ctx) => {
ctx.reply('Statistics...')
})
adminComposer.command('ban', (ctx) => {
// Ban logic
})
// Install admin composer on the bot
bot.use(adminComposer)
// Create a composer for handling different message types
const messageComposer = new Composer()
messageComposer.route(
(ctx) => {
if (ctx.msg?.text) return 'text'
if (ctx.msg?.photo) return 'photo'
if (ctx.msg?.document) return 'document'
},
{
text: (ctx) => console.log('Text message'),
photo: (ctx) => console.log('Photo'),
document: (ctx) => console.log('Document')
},
(ctx) => console.log('Other message type')
)
bot.on('message', messageComposer)
// Error boundary example
const safeComposer = bot.errorBoundary(
(err) => {
console.error('Error:', err.error.message)
},
(ctx) => {
// This might throw an error
throw new Error('Oops!')
}
)
bot.start()
See Also
- Bot - The main Bot class that extends Composer
- Context - The context object passed to middleware
- Middleware - Understanding middleware in grammY
- Filter Queries - Filter query syntax reference