When working with the Telegram Bot API, it is easy to expect that an album with multiple photos will arrive as a single event.
But Telegram works differently.
If a user sends an album of 3 photos, your bot doesn't receive one "post" object. Instead, it receives 3 separate updates in rapid succession:
Update #1 -> photo_1
Update #2 -> photo_2
Update #3 -> photo_3
instead of:
single_post -> [photo_1, photo_2, photo_3]
The Headache: Why this is hard
Handling these updates manually forces you to deal with a lot of "plumbing" code that has nothing to do with your actual bot logic:
Duplicate database records: Accidentally creating 3 posts instead of 1.
Race conditions: Multiple updates hitting your server at the exact same time.
Buffering & Timeouts: Deciding how long to wait for the "next" photo before assuming the group is complete.
Ordering: Ensuring the photos stay in the order the user intended.
Typical "infrastructure" code usually starts looking like this mess:
const mediaGroups = new Map();
// buffering logic...
// sorting logic...
// duplicate prevention...
// cleanup jobs...
// timeout handling...
At some point, you realize you are rewriting low-level infrastructure instead of building your actual features.
The Solution: telegram-media
I built telegram-media—a lightweight TypeScript library for Node.js that collects these scattered Telegram updates into a single, normalized object.
Installation
npm install telegram-media
Usage Example
Here is how you can use it to collect media and save it to a database (like Prisma) using a Redis storage backend:
const collector = createTelegramMediaGroup({
async onCollected(post) {
// This only fires ONCE per media group
await prisma.telegramPost.create({
data: mapCollectedPostToPrismaInput(post),
});
},
// Use Redis for distributed environments
storage: createRedisMediaGroupStorage(redisClient),
timeoutMs: 3000,
supportedMediaTypes: ["photo", "video", "audio"],
});
Why I Built This?
I ran into this problem while building a Telegram ingestion system for a personal project. At first, the logic seemed simple—just a small Set and a setTimeout.
Then the edge cases hit: distributed workers fighting over the same group, incomplete groups caused by network lag, and Redis synchronization issues. I extracted the logic into a standalone package so no one else has to solve this from scratch.
Key Features
- Media Group Aggregation — Automatically groups related Telegram updates into a single normalized post.
- Redis Support — Ready for production and distributed environments.
- Duplicate Prevention — Handles Telegram retry updates safely.
- Ordering — Preserves the original media sequence.
- TypeScript — Fully typed for a better developer experience.
Explore
What do you think?
I'd love to hear how others are handling media groups. Do you use a custom buffer, or do you just process each image individually and update the record as you go?
Let me know in the comments!

Top comments (0)