Session middleware provides persistent data storage for your bot, allowing you to remember information across updates. It attaches session data to every chat and makes it available on the context object under ctx.session.
session
Creates session middleware that loads and saves session data automatically.
function session < S , C extends Context >(
options ?: SessionOptions < S , C > | MultiSessionOptions < S , C >
) : MiddlewareFn < C & SessionFlavor < S >>
options
SessionOptions<S, C> | MultiSessionOptions<S, C>
Configuration options for session middleware
Returns: Middleware function that adds ctx.session to the context.
Example
import { Bot , session } from 'grammy'
interface SessionData {
messageCount : number
username ?: string
}
const bot = new Bot < Context & SessionFlavor < SessionData >>( 'YOUR_BOT_TOKEN' )
// Install session middleware
bot . use ( session ({
initial : () => ({ messageCount: 0 })
}))
bot . on ( 'message' , ( ctx ) => {
ctx . session . messageCount ++
ctx . reply ( `You sent ${ ctx . session . messageCount } messages` )
})
SessionOptions
Configuration options for single session middleware.
Session type. Defaults to 'single' if omitted.
Recommended. Function that produces an initial value for ctx.session. Called when the storage returns undefined for a session key. Must return a new value each time to avoid sharing session data between different chats.
Optional prefix to prepend to session keys. Useful for namespacing session data.
getSessionKey
(ctx: Context) => string | undefined
Custom function to generate session keys. By default, sessions are stored per chat using ctx.chatId. Return undefined to skip session loading for an update.
Storage adapter for reading and writing session data. Defaults to in-memory storage (data is lost on restart). See known storage adapters for database integrations.
Example with Options
import { Bot , session } from 'grammy'
interface SessionData {
count : number
started : Date
}
const bot = new Bot ( 'YOUR_BOT_TOKEN' )
bot . use ( session < SessionData >({
initial : () => ({
count: 0 ,
started: new Date ()
}),
prefix: 'mybot' ,
getSessionKey : ( ctx ) => {
// Separate sessions for each user in each chat
return ctx . chat ?. id && ctx . from ?. id
? ` ${ ctx . chat . id } : ${ ctx . from . id } `
: undefined
}
}))
MultiSessionOptions
Configuration for managing multiple independent sessions per update.
type MultiSessionOptions < S , C > = {
type : 'multi'
} & {
[ K in keyof S ] : SessionOptions < S [ K ], C >
}
Must be set to 'multi' for multi sessions
Each property of the session data object gets its own SessionOptions configuration.
Example
import { Bot , session } from 'grammy'
interface SessionData {
chat : { theme : string }
user : { language : string }
}
const bot = new Bot ( 'YOUR_BOT_TOKEN' )
bot . use ( session < SessionData >({
type: 'multi' ,
chat: {
initial : () => ({ theme: 'light' }),
// Store per chat
getSessionKey : ( ctx ) => ctx . chat ?. id ?. toString ()
},
user: {
initial : () => ({ language: 'en' }),
// Store per user
getSessionKey : ( ctx ) => ctx . from ?. id ?. toString ()
}
}))
bot . command ( 'theme' , ( ctx ) => {
ctx . session . chat . theme = 'dark'
ctx . reply ( 'Theme updated!' )
})
bot . command ( 'language' , ( ctx ) => {
ctx . session . user . language = 'es'
ctx . reply ( 'Language updated!' )
})
SessionFlavor
Context flavor that adds the session property.
interface SessionFlavor < S > {
get session () : S
set session ( session : S | null | undefined )
}
Session data for the current update. Reading or writing throws if getSessionKey returns undefined. Set to null or undefined to delete the session.
Warning: The type system does not include | undefined to avoid cumbersome null checks. Ensure session data is always initialized via the initial option or by assigning a value if empty.
Example
import { Context , SessionFlavor } from 'grammy'
interface SessionData {
pizzaCount : number
}
type MyContext = Context & SessionFlavor < SessionData >
const bot = new Bot < MyContext >( 'YOUR_BOT_TOKEN' )
bot . use ( session ({
initial : () => ({ pizzaCount: 0 })
}))
bot . command ( 'pizza' , ( ctx ) => {
ctx . session . pizzaCount ++
ctx . reply ( `🍕 ${ ctx . session . pizzaCount } ` )
})
bot . command ( 'reset' , ( ctx ) => {
// Delete the session
ctx . session = null
ctx . reply ( 'Session reset!' )
})
lazySession
Creates lazy session middleware that loads session data on-demand.
function lazySession < S , C extends Context >(
options ?: SessionOptions < S , C >
) : MiddlewareFn < C & LazySessionFlavor < S >>
Configuration options (same as regular session, but multi sessions are not supported)
Returns: Middleware function that adds lazy ctx.session to the context.
Lazy sessions load data only when you access ctx.session, reducing unnecessary database queries for updates your bot ignores.
Example
import { Bot , lazySession } from 'grammy'
interface SessionData {
score : number
}
const bot = new Bot ( 'YOUR_BOT_TOKEN' )
bot . use ( lazySession ({
initial : () => ({ score: 0 })
}))
// No database query here - session not accessed
bot . on ( 'message:sticker' , ( ctx ) => {
ctx . reply ( 'Nice sticker!' )
})
// Database query happens only on this line
bot . command ( 'score' , async ( ctx ) => {
const session = await ctx . session // Note the await!
session . score ++
ctx . reply ( `Score: ${ session . score } ` )
})
LazySessionFlavor
Context flavor for lazy sessions.
interface LazySessionFlavor < S > {
get session () : MaybePromise < S >
set session ( session : MaybePromise < S | null | undefined >)
}
Session data or a Promise of session data. First access triggers the database query; subsequent accesses return the cached value.
StorageAdapter
Interface for implementing custom storage backends.
interface StorageAdapter < T > {
read : ( key : string ) => MaybePromise < T | undefined >
write : ( key : string , value : T ) => MaybePromise < void >
delete : ( key : string ) => MaybePromise < void >
has ?: ( key : string ) => MaybePromise < boolean >
readAllKeys ?: () => Iterable < string > | AsyncIterable < string >
readAllValues ?: () => Iterable < T > | AsyncIterable < T >
readAllEntries ?: () => Iterable <[ string , T ]> | AsyncIterable <[ string , T ]>
}
read
(key: string) => MaybePromise<T | undefined>
required
Reads a value from storage. Returns undefined if the key doesn’t exist.
write
(key: string, value: T) => MaybePromise<void>
required
Writes a value to storage.
delete
(key: string) => MaybePromise<void>
required
Deletes a value from storage.
has
(key: string) => MaybePromise<boolean>
Optional. Checks if a key exists in storage.
readAllKeys
() => Iterable<string> | AsyncIterable<string>
Optional. Lists all keys in storage.
readAllValues
() => Iterable<T> | AsyncIterable<T>
Optional. Lists all values in storage.
readAllEntries
() => Iterable<[string, T]> | AsyncIterable<[string, T]>
Optional. Lists all key-value pairs in storage.
Example Storage Adapter
import { StorageAdapter } from 'grammy'
import Redis from 'ioredis'
class RedisAdapter < T > implements StorageAdapter < T > {
constructor ( private redis : Redis ) {}
async read ( key : string ) : Promise < T | undefined > {
const value = await this . redis . get ( key )
return value ? JSON . parse ( value ) : undefined
}
async write ( key : string , value : T ) : Promise < void > {
await this . redis . set ( key , JSON . stringify ( value ))
}
async delete ( key : string ) : Promise < void > {
await this . redis . del ( key )
}
}
const redis = new Redis ()
bot . use ( session ({
initial : () => ({ count: 0 }),
storage: new RedisAdapter ( redis )
}))
MemorySessionStorage
Built-in in-memory storage adapter.
class MemorySessionStorage < S > implements StorageAdapter < S > {
constructor ( timeToLive ?: number )
read ( key : string ) : S | undefined
write ( key : string , value : S ) : void
delete ( key : string ) : void
has ( key : string ) : boolean
readAllKeys () : string []
readAllValues () : S []
readAllEntries () : [ string , S ][]
}
Optional TTL in milliseconds. Sessions older than this are automatically discarded. Defaults to Infinity (never expire).
Note: This adapter stores data in RAM. All data is lost when your bot process restarts. Use a database adapter for production.
Example
import { Bot , session , MemorySessionStorage } from 'grammy'
const bot = new Bot ( 'YOUR_BOT_TOKEN' )
// Sessions expire after 1 hour
bot . use ( session ({
initial : () => ({ data: [] }),
storage: new MemorySessionStorage ( 60 * 60 * 1000 )
}))
enhanceStorage
Enhances a storage adapter with session migrations and expiry dates.
function enhanceStorage < T >(
options : MigrationOptions < T >
) : StorageAdapter < T >
options
MigrationOptions<T>
required
Enhancement options Show MigrationOptions properties
storage
StorageAdapter<Enhance<T>>
required
The storage adapter to enhance
Mapping from version numbers to migration functions
TTL in milliseconds for session expiry
Example with Migrations
import { enhanceStorage , MemorySessionStorage } from 'grammy'
interface SessionV1 {
counter : number
}
interface SessionV2 {
count : number
lastUpdate : number
}
const storage = enhanceStorage ({
storage: new MemorySessionStorage (),
migrations: {
1 : ( old : any ) => ({ counter: 0 }), // Initialize v1
2 : ( old : SessionV1 ) : SessionV2 => ({
count: old . counter ,
lastUpdate: Date . now ()
})
},
millisecondsToLive: 24 * 60 * 60 * 1000 // 24 hours
})
bot . use ( session ({
initial : () : SessionV2 => ({ count: 0 , lastUpdate: Date . now () }),
storage
}))
Complete Example
import { Bot , Context , session , SessionFlavor } from 'grammy'
// Define session structure
interface SessionData {
messageCount : number
lastCommand ?: string
preferences : {
notifications : boolean
theme : 'light' | 'dark'
}
}
// Create custom context type
type MyContext = Context & SessionFlavor < SessionData >
const bot = new Bot < MyContext >( 'YOUR_BOT_TOKEN' )
// Install session middleware
bot . use ( session ({
initial : () : SessionData => ({
messageCount: 0 ,
preferences: {
notifications: true ,
theme: 'light'
}
})
}))
// Use session data
bot . on ( 'message' , ( ctx ) => {
ctx . session . messageCount ++
if ( ctx . session . messageCount === 1 ) {
ctx . reply ( 'Welcome! This is your first message.' )
}
})
bot . command ( 'stats' , ( ctx ) => {
const { messageCount , lastCommand , preferences } = ctx . session
ctx . reply (
`📊 Your Stats: \n ` +
`Messages: ${ messageCount } \n ` +
`Last command: ${ lastCommand || 'none' } \n ` +
`Theme: ${ preferences . theme } \n ` +
`Notifications: ${ preferences . notifications ? 'on' : 'off' } `
)
})
bot . command ( 'theme' , ( ctx ) => {
const current = ctx . session . preferences . theme
ctx . session . preferences . theme = current === 'light' ? 'dark' : 'light'
ctx . reply ( `Theme changed to ${ ctx . session . preferences . theme } ` )
})
bot . command ( 'reset' , ( ctx ) => {
ctx . session = null
ctx . reply ( 'Session cleared!' )
})
bot . start ()
See Also