# How Telegram Desktop Implements Chat Invite Links and Chatlist Invites

> Discover how Telegram Desktop implements chat invite links and chatlist invites using parallel subsystems and MTProto API. Learn about messages.checkChatInvite and chatlists.exportChatlistInvite.

- Repository: [Telegram Desktop/tdesktop](https://github.com/telegramdesktop/tdesktop)
- Tags: internals
- Published: 2026-04-05

---

**Telegram Desktop processes chat invite links and chatlist invites through parallel subsystems that share an MTProto API layer but diverge in UI handling, using `messages.checkChatInvite` for individual invites and `chatlists.exportChatlistInvite` for filtered chat lists.**

The telegramdesktop/tdesktop codebase handles both individual chat invitations and filtered chat list sharing through a sophisticated multi-layered architecture. This implementation separates API communication, data persistence, and user interface concerns while maintaining strict adherence to the MTProto protocol. Understanding these internals reveals how the client manages permission checks, preview generation, and batch chat subscriptions.

## Core API Architecture

Both subsystems funnel requests through `ApiWrap::request()` in **apiwrap.cpp** (lines 366-370), which provides generic error handling and authentication layers. The implementation maps MTProto TL methods to C++ wrappers that bridge the protocol buffer layer with the Qt-based UI framework.

The primary API distinctions include:

- **Individual invites**: Use `messages.checkChatInvite` and `messages.importChatInvite` methods
- **Chatlist invites**: Use `chatlists.exportChatlistInvite`, `chatlists.editExportedInvite`, and `chatlists.joinChatlistInvite` methods

## Individual Chat Invite Links Implementation

### Checking and Validating Invites

When a user triggers an invite link—either by clicking an external URL or pasting a hash—the client calls `CheckChatInvite()` in **api/api_chat_invite.cpp** (lines 61-73). This function sends the `messages.checkChatInvite` query to the server and handles three possible response constructors:

1. `messages.chatInvite` - Standard invitation requiring user confirmation
2. `messages.chatInviteAlready` - User is already a member
3. `messages.chatInvitePeek` - Preview of a private invite

If the invite requires a subscription payment, the UI instantiates `ConfirmSubscriptionBox` (lines 19-46); otherwise, it creates `ConfirmInviteBox` (lines 104-112).

### UI Presentation and Join Flow

The `ConfirmInviteBox` class in **boxes/peers/edit_peer_invite_links.cpp** (constructor at line 72) renders the invitation preview, displaying the chat title, avatar, participant count, and expiry information. When the user presses the join button, `SubmitChatInvite()` (lines 51-66 in **api/api_chat_invite.cpp**) dispatches `messages.importChatInvite` to the server.

```cpp
// api/api_chat_invite.cpp:61-73
void CheckChatInvite(
        not_null<Window::SessionController*> controller,
        const QString &hash,
        ChannelData *invitePeekChannel,
        Fn<void()> loaded) {
    const auto session = &controller->session();
    const auto weak = base::make_weak(controller);
    session->api().checkChatInvite(hash,
        [=](const MTPChatInvite &result) {
            // Handle constructors and show ConfirmInviteBox
        },
        [=](const MTP::Error &error) { /* Error handling */ });
}

```

On successful joining, the client opens the chat history via `Window::SessionController::showPeerHistory`.

## Chatlist Invite Links Implementation

Chatlist invites (also called folder invites) allow users to share entire filtered sets of chats through a single link. Unlike individual invites, these operate on the `Data::ChatFilterLink` data structure defined in **data/data_chat_filters.h**.

### Exporting Chatlist Invites

When a user clicks **Export link** in the filter editor, `ExportFilterLink()` in **boxes/filters/edit_filter_links.cpp** (lines 1080-1087) constructs an `MTP_vector<MTPInputPeer>` from the filter's chat list and calls `chatlists.exportChatlistInvite`.

```cpp
// boxes/filters/edit_filter_links.cpp:1080-1087
void ExportFilterLink(
        FilterId id,
        const std::vector<not_null<PeerData*>> &peers,
        Fn<void(Data::ChatFilterLink)> done,
        Fn<void(QString)> fail) {
    const auto session = &peers.front()->session();
    auto mtpPeers = peers |
        ranges::views::transform([](not_null<PeerData*> p){ 
            return MTPInputPeer(p->input()); 
        })
        | ranges::to<QVector<MTPInputPeer>>();
    session->api().request(MTPchatlists_ExportChatlistInvite(
            MTP_inputChatlistDialogFilter(MTP_int(id)),
            MTP_string(), // Empty title for default
            MTP_vector<MTPInputPeer>(std::move(mtpPeers))))
        .done([=](const MTPchatlists_ExportedChatlistInvite &result) {
            // Store via Data::ChatFilters::add()
        })
        .fail([=](const MTP::Error &error) { fail(error.type()); })
        .send();
}

```

The server returns a `ChatlistExportedInvite` containing a unique slug, which the client persists through `Data::ChatFilters::add()` in **data/data_chat_filters.cpp** (lines 543-553).

### Editing and Managing Links

The `EditLinkChats()` function (lines 1040-1064 in **boxes/filters/edit_filter_links.cpp**) handles modifications to existing chatlist invites. It sends `chatlists.editExportedInvite` with updated peer vectors while preserving the original URL slug.

```cpp
// boxes/filters/edit_filter_links.cpp:1040-1064
void EditLinkChats(
        const Data::ChatFilterLink &link,
        base::flat_set<not_null<PeerData*>> peers,
        Fn<void(QString)> done) {
    const auto session = &peers.front()->session();
    auto mtpPeers = peers |
        ranges::views::transform([](not_null<PeerData*> p){ 
            return MTPInputPeer(p->input()); 
        })
        | ranges::to<QVector<MTPInputPeer>>();
    session->api().request(MTPchatlists_EditExportedInvite(
            MTP_flags(MTPchatlists_EditExportedInvite::Flag::f_peers),
            MTP_inputChatlistDialogFilter(MTP_int(link.id)),
            MTP_string(link.url),
            MTPstring(), // Title unchanged
            MTP_vector<MTPInputPeer>(std::move(mtpPeers))))
        .done([=](const MTPExportedChatlistInvite &) { done(QString()); })
        .fail([=](const MTP::Error &error) { done(error.type()); })
        .send();
}

```

## Error Handling and UI Feedback

Both subsystems rely on `ApiWrap::request()` for error propagation. Specific error strings like `INVITE_REQUEST_SENT`, `USERS_TOO_MUCH`, and generic "bad link" responses are mapped to localized toast notifications in `CheckChatInvite` (lines 93-115) and through `ShowSaveError` / `ShowEmptyLinkError` for chatlist operations (lines 108-115).

The UI components for displaying links include:

- `InviteLinkLabel` and `InviteLinkButtons` in **ui/controls/invite_link_label.h/.cpp** and **ui/controls/invite_link_buttons.h/.cpp**
- `LinkRow` in **boxes/filters/edit_filter_links.cpp** (lines 35-53) for chatlist link listings

## Key Files and Architecture

The implementation maintains strict separation across three layers:

- **API Layer**: **api/api_chat_invite.cpp**, **apiwrap.cpp**, and **mtproto/scheme/api.tl** handle protocol communication
- **Data Layer**: **data/data_chat_filters.cpp** persists chatlist invite state
- **UI Layer**: **boxes/peers/edit_peer_invite_links.cpp** and **boxes/filters/edit_filter_links.cpp** manage user interactions

## Summary

- Telegram Desktop implements chat invite links and chatlist invites as parallel subsystems with distinct MTProto methods but shared error handling infrastructure
- Individual invites use `messages.checkChatInvite` and `messages.importChatInvite`, processed through `CheckChatInvite()` and `SubmitChatInvite()` in **api/api_chat_invite.cpp**
- Chatlist invites rely on `chatlists.exportChatlistInvite` and `chatlists.editExportedInvite`, implemented in **boxes/filters/edit_filter_links.cpp**
- The `Data::ChatFilterLink` structure in **data/data_chat_filters.h** persists chatlist invite metadata including slugs and peer lists
- All API requests flow through `ApiWrap::request()` in **apiwrap.cpp**, which attaches standardized error handling and authentication

## Frequently Asked Questions

### What is the difference between chat invite links and chatlist invites in Telegram Desktop?

Chat invite links provide access to individual groups or channels through `messages.checkChatInvite`, while chatlist invites allow sharing entire filtered chat collections via `chatlists.exportChatlistInvite`. The former uses the `ConfirmInviteBox` UI component, whereas the latter utilizes `Data::ChatFilterLink` structures and the `LinkRow` class for management.

### How does the client handle expired or invalid invite links?

Invalid links trigger error responses through `ApiWrap::request()` that are caught in `CheckChatInvite` (lines 93-115) and translated to localized toast notifications. Specific error types like `INVITE_REQUEST_SENT` or `USERS_TOO_MUCH` generate distinct user-facing messages without crashing the application.

### Which data structure stores chatlist invite information?

The `Data::ChatFilterLink` structure defined in **data/data_chat_filters.h** stores chatlist invite metadata including the URL slug, title, and associated chat list. The `Data::ChatFilters` manager class maintains the mapping between filter IDs and these link objects in **data/data_chat_filters.cpp**.

### Where does the actual API request construction happen for joining a chat?

The `SubmitChatInvite()` function in **api/api_chat_invite.cpp** (lines 51-66) constructs the `MTPmessages_ImportChatInvite` request using the invite hash, while chatlist modifications use `EditLinkChats()` in **boxes/filters/edit_filter_links.cpp** to build `MTPchatlists_EditExportedInvite` requests with updated peer vectors.