The InputFile class represents a file to be uploaded to Telegram. It can handle local file paths, URLs, buffers, streams, and various other file sources.
Constructor
Creates a new InputFile instance from various file sources.
new InputFile(
file: string | Blob | URL | Uint8Array | ReadableStream | Iterable | AsyncIterable | (() => any),
filename?: string
)
file
string | Blob | URL | Uint8Array | ReadableStream | ...
required
The file data source. Can be:
- String: Local file path (Deno only) or identifier
- Blob: File blob object
- URL: URL object pointing to a file
- Uint8Array: Raw file bytes
- ReadableStream: Stream of file data
- Iterable/AsyncIterable: Iterator yielding
Uint8Array chunks
- Function: Supplier function returning any of the above
Note: Do not pass HTTP URLs as strings. Use new URL(...) or pass file IDs directly to API methods.
Optional filename. If omitted, grammY attempts to guess it from the file source (file paths and URLs).
Examples
import { InputFile } from 'grammy'
// From local file path (Deno)
const file1 = new InputFile('/path/to/image.jpg')
// From URL object
const file2 = new InputFile(new URL('https://grammy.dev/images/grammY.png'))
// From Blob
const blob = new Blob(['Hello, world!'], { type: 'text/plain' })
const file3 = new InputFile(blob, 'hello.txt')
// From Uint8Array buffer
const buffer = new Uint8Array([0xFF, 0xD8, 0xFF, 0xE0])
const file4 = new InputFile(buffer, 'image.jpg')
// From ReadableStream
const response = await fetch('https://example.com/file.pdf')
const file5 = new InputFile(response.body, 'document.pdf')
// From supplier function (lazy loading)
const file6 = new InputFile(
async () => {
const data = await fetchFileFromDatabase()
return data
},
'data.bin'
)
Properties
The filename of the file. May be undefined if not specified and couldn’t be guessed from the source.
Usage with Bot API Methods
You can use InputFile instances with any method that accepts file uploads.
Sending Photos
import { Bot, InputFile } from 'grammy'
const bot = new Bot('YOUR_BOT_TOKEN')
bot.command('photo', async (ctx) => {
// From local file
await ctx.replyWithPhoto(new InputFile('/path/to/photo.jpg'))
// From URL
await ctx.replyWithPhoto(
new InputFile(new URL('https://grammy.dev/images/grammY.png'))
)
// With caption
await ctx.replyWithPhoto(
new InputFile('/path/to/photo.jpg'),
{ caption: 'Check out this photo!' }
)
})
Sending Documents
bot.command('document', async (ctx) => {
const doc = new InputFile('/path/to/document.pdf')
await ctx.replyWithDocument(doc, {
caption: 'Here is your document',
thumbnail: new InputFile('/path/to/thumbnail.jpg')
})
})
Sending Audio
bot.command('audio', async (ctx) => {
await ctx.replyWithAudio(
new InputFile('/path/to/song.mp3'),
{
title: 'My Song',
performer: 'Artist Name',
thumbnail: new InputFile('/path/to/cover.jpg')
}
)
})
Sending Video
bot.command('video', async (ctx) => {
await ctx.replyWithVideo(
new InputFile('/path/to/video.mp4'),
{
caption: 'Watch this video!',
width: 1920,
height: 1080,
thumbnail: new InputFile('/path/to/thumbnail.jpg')
}
)
})
Sending Stickers
bot.command('sticker', async (ctx) => {
await ctx.replyWithSticker(new InputFile('/path/to/sticker.webp'))
})
Sending Voice Messages
bot.command('voice', async (ctx) => {
await ctx.replyWithVoice(
new InputFile('/path/to/voice.ogg'),
{ caption: 'Voice message' }
)
})
Sending Video Notes
bot.command('videonote', async (ctx) => {
await ctx.replyWithVideoNote(
new InputFile('/path/to/video_note.mp4'),
{ length: 240 }
)
})
You can use InputFile in media groups to send albums.
import { InputMediaBuilder } from 'grammy'
bot.command('album', async (ctx) => {
await ctx.replyWithMediaGroup([
InputMediaBuilder.photo(new InputFile('/path/to/photo1.jpg')),
InputMediaBuilder.photo(new InputFile('/path/to/photo2.jpg'), {
caption: 'Second photo'
}),
InputMediaBuilder.video(new InputFile('/path/to/video.mp4'))
])
})
Advanced Usage
Lazy Loading Files
Use a supplier function to load files only when needed:
bot.command('lazy', async (ctx) => {
const file = new InputFile(
async () => {
console.log('Fetching file from database...')
const data = await database.getFile(ctx.from.id)
return data
},
'user_file.pdf'
)
// File is only fetched when this line executes
await ctx.replyWithDocument(file)
})
Streaming Large Files
For large files, use streams to avoid loading the entire file into memory:
import { createReadStream } from 'fs'
bot.command('large', async (ctx) => {
// Node.js example
const stream = createReadStream('/path/to/large_video.mp4')
await ctx.replyWithVideo(
new InputFile(stream, 'large_video.mp4'),
{ supports_streaming: true }
)
})
Fetching from URLs
bot.command('download', async (ctx) => {
const url = new URL('https://example.com/image.jpg')
const file = new InputFile(url)
await ctx.replyWithPhoto(file, {
caption: 'Downloaded from URL'
})
})
Working with Blobs (Browser/Deno)
bot.command('blob', async (ctx) => {
// Create a text file from string
const blob = new Blob(
['This is the file content'],
{ type: 'text/plain' }
)
await ctx.replyWithDocument(
new InputFile(blob, 'example.txt')
)
})
Setting Custom Thumbnails
Many file types accept custom thumbnails:
bot.command('thumbnail', async (ctx) => {
await ctx.replyWithVideo(
new InputFile('/path/to/video.mp4'),
{
thumbnail: new InputFile('/path/to/custom_thumbnail.jpg'),
caption: 'Video with custom thumbnail'
}
)
})
Telegram provides file identifiers for uploaded files. You can reuse these without creating new InputFile instances:
bot.on('message:photo', async (ctx) => {
// Get file_id from received photo
const fileId = ctx.message.photo[0].file_id
// Reuse the file_id (no InputFile needed)
await ctx.api.sendPhoto(ctx.chat.id, fileId, {
caption: 'Same photo, reused via file_id'
})
// Download and re-upload (requires InputFile)
const file = await ctx.api.getFile(fileId)
const path = file.file_path
await ctx.replyWithPhoto(
new InputFile(new URL(`https://api.telegram.org/file/bot${bot.token}/${path}`))
)
})
Deno
In Deno, you can pass file paths directly:
// Works in Deno
const file = new InputFile('/path/to/file.jpg')
await ctx.replyWithPhoto(file)
Node.js
In Node.js, use fs module for file operations:
import { createReadStream } from 'fs'
import { InputFile } from 'grammy'
// Use streams
const stream = createReadStream('/path/to/file.jpg')
const file = new InputFile(stream, 'file.jpg')
await ctx.replyWithPhoto(file)
Browser
In browsers, work with Blob or File objects:
// From file input
const input = document.querySelector('input[type="file"]')
const file = input.files[0] // This is a Blob
await ctx.replyWithDocument(new InputFile(file))
Error Handling
bot.command('upload', async (ctx) => {
try {
const file = new InputFile('/path/to/file.jpg')
await ctx.replyWithPhoto(file)
} catch (error) {
console.error('Upload failed:', error)
await ctx.reply('Failed to upload file')
}
})
Important Notes
- File Reuse:
InputFile instances cannot be reused. Create a new instance for each upload operation.
- File Paths: Only work in Deno environments. Use streams in Node.js.
- HTTP URLs: Don’t pass HTTP URLs as strings. Use
new URL(...) instead.
- File IDs: If you have a file_id from Telegram, pass it directly to API methods without wrapping in
InputFile.
- File Size Limits: Telegram has file size limits (20 MB for photos, 50 MB for other files via bot API).
Complete Example
import { Bot, InputFile, InputMediaBuilder } from 'grammy'
const bot = new Bot('YOUR_BOT_TOKEN')
// Single photo
bot.command('photo', async (ctx) => {
await ctx.replyWithPhoto(
new InputFile('/photos/grammY.png'),
{ caption: 'grammY logo' }
)
})
// Document with thumbnail
bot.command('pdf', async (ctx) => {
await ctx.replyWithDocument(
new InputFile('/documents/guide.pdf'),
{
caption: 'User guide',
thumbnail: new InputFile('/thumbnails/pdf_thumb.jpg')
}
)
})
// Media group
bot.command('gallery', async (ctx) => {
await ctx.replyWithMediaGroup([
InputMediaBuilder.photo(new InputFile('/gallery/img1.jpg'), {
caption: 'Photo 1'
}),
InputMediaBuilder.photo(new InputFile('/gallery/img2.jpg'), {
caption: 'Photo 2'
}),
InputMediaBuilder.video(new InputFile('/gallery/video.mp4'))
])
})
// From URL
bot.command('web', async (ctx) => {
const url = new URL('https://grammy.dev/images/grammY.png')
await ctx.replyWithPhoto(new InputFile(url))
})
// Lazy loading
bot.command('user_file', async (ctx) => {
const file = new InputFile(
async () => {
const userData = await getUserData(ctx.from.id)
return userData.profilePicture
},
'profile.jpg'
)
await ctx.replyWithPhoto(file)
})
bot.start()
See Also