Skip to main content

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>[])
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