Skip to main content
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>[])
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>
middleware
Middleware<C>[]
required
The middleware function(s) to register
return
Composer<C>
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)

on

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
fallback
Middleware<C>
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
Middleware<C>[]
required
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>
return
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

Build docs developers (and LLMs) love