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