How to Collect Telegram Media Groups in Node.js

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!