How Forum Topics Work in Telegram Desktop Channels: Data::Forum and Data::ForumTopic Architecture

Telegram Desktop implements channel forum topics through the Data::Forum container class that manages topic loading and caching, and the Data::ForumTopic thread-derived class that handles individual topic state, unread counters, and UI integration.

Telegram Desktop organizes channel forum topics using a specialized data layer centered around two core classes in the Data namespace. Understanding how forum topics work in channels reveals the complete lifecycle of topic management—from server synchronization to chat list rendering.

Core Data Architecture

The implementation relies on two primary classes defined in Telegram/SourceFiles/data/data_forum.h and Telegram/SourceFiles/data/data_forum_topic.h:

  • Data::Forum: Acts as the container and manager for all topics within a channel. It handles network requests, maintains the topic cache, and coordinates lifecycle events.
  • Data::ForumTopic: Represents a single topic thread, storing metadata such as title, icon, color, creator information, and unread state.

Both classes inherit from the generic Thread hierarchy (see data_thread.h), which allows ForumTopic to utilize the same interface as regular chats for features like chatListMessage() and unreadStateFor() while adding forum-specific functionality.

Loading and Caching Forum Topics

When a user opens a channel with topics enabled, the system initiates a structured loading sequence in Forum::preloadTopics() defined in Telegram/SourceFiles/data/data_forum.cpp.

The process follows three steps:

  1. API Request: The client sends MTPmessages_GetForumTopics to the Telegram servers.
  2. Response Processing: Forum::applyReceivedTopics() iterates through the MTPVector<MTPForumTopic> payload, calling applyTopicAdded() for each entry.
  3. Object Creation: New topics are instantiated and stored in _topics, a base::flat_map<MsgId, std::unique_ptr<ForumTopic>>. Deleted topics are tracked separately in _topicsDeleted.

The topic list UI updates automatically because each ForumTopic registers with Dialogs::MainList _topicsList during construction via _list(_forum->topicsList()).

Topic Lifecycle Operations

The Data::Forum and Data::ForumTopic classes expose specific methods for managing topic state transitions:

Creating Topics

Client-side topic creation begins with Forum::reserveCreatingId(), which allocates a temporary root ID. The ForumTopic::creating() method initializes the temporary object, and ForumTopic::setRealRootId() updates it once the server confirms creation. If cancelled, ForumTopic::discard() removes the temporary entry.

Editing Metadata

Title changes invoke ForumTopic::applyTitle(), while icon and color updates use applyIconId() and applyColorId(). These methods trigger UI refreshes via invalidateTitleWithIcon() and session().changes().topicUpdated(this, UpdateFlag::…). The editing UI is implemented in Telegram/SourceFiles/boxes/peers/edit_forum_topic_box.h.

Closing and Opening

The ForumTopic::setClosedAndSave(bool) method sends messages.EditForumTopic to the server. The closed state is stored in Flag::Closed and exposed through canToggleClosed() for UI validation.

Deleting Topics

When deletion occurs, Forum::applyTopicDeleted() removes the entry from _topics and fires the _topicDestroyed event to notify subscribers.

Marking as Read

Unread state management delegates to RepliesList::readTill() through ForumTopic::readTillEnd(), with counters propagated to the chat list via chatListBadgesState().

Chat List Integration and Unread Counters

Data::ForumTopic implements virtual methods from the Thread base class to integrate with Telegram Desktop's chat list system:

  • chatListMessage() and chatListMessageKnown() return the last visible message for preview.
  • chatListUnreadState() constructs a Dialogs::UnreadState using _replies->displayedUnreadCount().
  • chatListBadgesState() handles special cases like "empty inbox" topics (lines 215-225 of data_forum_topic.cpp).

The ForumTopic::subscribeToUnreadChanges() method connects RepliesList updates to the chat list entry and invalidates the recent topics cache via _forum->recentTopicsInvalidate(this).

UI Rendering and Icon Handling

Userpic Rendering

The ForumTopic::paintUserpic() method (lines 381-425 of data_forum_topic.cpp) handles visual representation:

  • Custom emoji icons (_icon) render using Ui::Text::LimitedLoopsEmoji.
  • Standard topics display a colored square with the first non-emoji letter via ForumTopicIconFrame.
  • General topics use ForumTopicGeneralIconFrame.

Title and Icon Composition

The ForumTopic::titleWithIcon() method returns a TextWithEntities object constructed by ForumTopicIconWithTitle() (lines 665-672), combining the topic title with its icon representation.

Emoji Encoding

Topics store icons as custom emoji entities (topic_icon: or topic_general:). The TopicIconEmojiEntity() function builds these strings (lines 201-207), while ParseTopicIconEmojiEntity() parses them back into TopicIconDescriptor structures (lines 210-229). Color mappings are handled in Telegram/SourceFiles/data/data_forum_icons.h.

Practical Implementation Examples

Enumerating All Topics in a Channel

To iterate through existing topics in a forum channel:

// Assume we have a not_null<ChannelData*> channel
auto *forum = channel->forum();          // Data::Forum*
forum->enumerateTopics([](not_null<Data::ForumTopic*> topic) {
    qDebug() << "Topic:" << topic->title()
             << "ID:" << topic->rootId()
             << "Closed:" << topic->closed();
});

This utilizes ChannelData::forum() and Forum::enumerateTopics() defined in data_forum.h.

Creating a New Topic

Client-side topic creation requires reserving a temporary ID before server confirmation:

auto *forum = channel->forum();
MsgId tempId = forum->reserveCreatingId(u"New Topic"_q, /*colorId=*/0x6FB9F0,
                                         /*iconId=*/0);
auto *topic = forum->enforceTopicFor(tempId);   // Returns the freshly created ForumTopic
topic->applyTitle(u"New Topic"_q);
topic->applyColorId(0x6FB9F0);
topic->setClosedAndSave(false);                // Sends EditForumTopic to the server

Key methods include Forum::reserveCreatingId(), Forum::enforceTopicFor(), and ForumTopic::setClosedAndSave().

Closing an Existing Topic

To programmatically close a topic after verifying permissions:

auto *topic = forum->topicFor(existingRootId);
if (topic && topic->canToggleClosed()) {
    topic->setClosedAndSave(true);   // Close and sync with the server
}

The canToggleClosed() method validates the action before setClosedAndSave() transmits the update.

Reacting to Unread Count Changes

Subscribe to unread changes for custom UI badges:

auto *topic = forum->topicFor(rootId);
topic->replies()->unreadCountValue() |
    rpl::filter([](std::optional<int> v) { return v && *v > 0; }) |
    rpl::start_with_next([=](std::optional<int>) {
        // Show a custom badge in the UI
        showBadgeForTopic(topic->rootId());
    }, topic->lifetime());

While ForumTopic::subscribeToUnreadChanges() automatically updates the chat list, this demonstrates manual subscription to RepliesList::unreadCountValue().

Summary

  • Data::Forum manages the collection of topics in a channel, handling loading via MTPmessages_GetForumTopics and caching in _topics.
  • Data::ForumTopic inherits from Thread, enabling seamless chat list integration while adding forum-specific properties like icons and closure states.
  • Topic creation uses temporary IDs via reserveCreatingId() until server confirmation updates the real root ID.
  • Unread counters flow from RepliesList through chatListBadgesState() to update the UI reactively.
  • Custom icons are encoded as emoji entities and rendered through paintUserpic() using specialized frames or limited-loop emoji rendering.

Frequently Asked Questions

How does Telegram Desktop load forum topics when opening a channel?

When a channel opens, Forum::preloadTopics() sends MTPmessages_GetForumTopics to the server. The response handler Forum::applyReceivedTopics() iterates through the returned vector, calling applyTopicAdded() for each topic to create or update ForumTopic objects stored in the _topics map.

What is the relationship between Data::ForumTopic and the Thread class?

Data::ForumTopic inherits from the Thread base class (defined in data_thread.h), which allows it to implement standard chat interfaces like chatListMessage() and unreadStateFor(). This inheritance enables topics to appear in the chat list alongside regular conversations while maintaining specialized forum behavior.

How are custom emoji icons stored and rendered for forum topics?

Topic icons are stored as custom emoji entities using TopicIconEmojiEntity() which generates strings like topic_icon:. The ForumTopic::paintUserpic() method renders these using Ui::Text::LimitedLoopsEmoji for custom emojis, or falls back to colored letter frames for standard topics.

What happens when a user deletes a forum topic?

Deletion triggers Forum::applyTopicDeleted(), which removes the topic from the _topics map and fires the _topicDestroyed event. This signals all UI components to remove the topic from the chat list and clean up associated resources, ensuring reactive updates across the application.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →