# How Telegram Desktop Manages Multi-Account Sessions and the Tray Accounts Menu

> Discover how Telegram Desktop manages multi-account sessions and the tray accounts menu. Learn about its memory and disk state serialization for seamless switching between accounts.

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

---

**Telegram Desktop handles multi-account sessions by maintaining separate MTProto authentication instances for each account in memory while serializing state to disk, with the tray accounts menu providing the UI layer for instantaneous switching between active sessions.**

Telegram Desktop allows users to operate multiple accounts simultaneously without restarting the application. This capability relies on a strict separation between **runtime session management** and **persistent storage**, coordinated through the tray accounts menu. Understanding how the client manages these multi-account sessions reveals the architecture behind seamless account switching and system tray integration.

## Session Architecture: Runtime vs. Persistent Storage

The application distinguishes between the active in-memory representation of an account and its on-disk persistence, enabling fast switching while maintaining data integrity across launches.

### Main::Account: The Runtime Session Object

Each logged-in session is represented by **`Main::Account`**, defined in [[`main_account.h`](https://github.com/telegramdesktop/tdesktop/blob/main/main_account.h)](https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/main/main_account.h) and implemented in [[`main_account.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/main_account.cpp)](https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/main/main_account.cpp). This class encapsulates a single MTProto connection and holds the **`MTP::AuthKey`**, **`MTP::Instance`**, network connection handler, updates manager, and a view into **`Core::Settings`** specific to that account.

All active accounts reside in a **`base::flat_map<PeerId, std::unique_ptr<Main::Account>>`** maintained by **`Main::Session`**. The currently active account is accessible via **`Main::Session::current()`**, which returns a pointer to the focused session. Switching accounts updates this pointer and propagates changes throughout the UI layer.

### Storage::Account: Persistent State Management

Persistent account data is handled by **`Storage::Account`**, declared in [[`storage_account.h`](https://github.com/telegramdesktop/tdesktop/blob/main/storage_account.h)](https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/storage/storage_account.h) and implemented in [[`storage_account.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/storage_account.cpp)](https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/storage/storage_account.cpp). This class manages serialization of authentication keys, user IDs, cached data, and account-specific settings to the local filesystem.

During application startup, **`Storage::Manager`** enumerates all stored account files and instantiates corresponding **`Main::Account`** objects, registering them within **`Main::Session`** to restore the previous session state.

## Managing Account Lifecycles

The application provides programmatic and UI-driven mechanisms for adding and removing accounts, both guarded by system limits.

### Adding New Accounts

The **"Add Account"** entry in the tray menu triggers **`Main::Account::addNew()`**, which creates a fresh **`Storage::Account`**, generates a new authentication key, and initiates the MTProto login flow. This method accepts default authentication parameters and UI show requests to handle the onboarding interface.

### Logging Out and Account Removal

The **"Log out"** action invokes **`Main::Account::logout()`** on the current session. This method clears the corresponding **`Storage::Account`** file from disk, removes the entry from the session list, and optionally purges the local data folder. The runtime object is destroyed and removed from the **`Main::Session`** container.

### Account Limits Enforcement

Both adding and removing actions respect the **`limits::accounts`** constant. The UI layer disables the "Add Account" menu entry when the maximum number of accounts is reached, preventing unauthorized session creation.

## Tray Accounts Menu Implementation

The system tray integration lives in [[`tray_accounts_menu.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/tray_accounts_menu.cpp)](https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/tray_accounts_menu.cpp) with its interface defined in [[`tray_accounts_menu.h`](https://github.com/telegramdesktop/tdesktop/blob/main/tray_accounts_menu.h)](https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/tray_accounts_menu.h).

### Menu Construction and Population

**`Tray::AccountsMenu::create()`** constructs a platform-native menu (typically `QMenu`) and iterates over **`Main::Session::list()`** to generate entries for each account. Each menu item displays the account avatar, username via **`UserData::userName()`**, and a checkmark indicator (**`st::trayAccountCheck`**) for the currently active session. The "Add Account" and "Log out" actions are appended as distinct entries at the bottom of the menu.

### Handling User Interactions

Clicking an account entry invokes **`Main::Session::switchTo(accountId)`**, where `accountId` is the **`PeerId`** of the target account. This updates **`Main::Session::current()`** and emits **`sessionChanged()`**, triggering UI refreshes across window titles, chat lists, and notification handlers. The "Add Account" and "Log out" items delegate to their respective **`Main::Account`** methods described above.

### Automatic UI Synchronization

**`Tray::AccountsMenu`** subscribes to **`Main::Session::accountsChanged`** signals. Any addition, removal, or activation event triggers a complete menu rebuild, ensuring the tray interface remains synchronized with the underlying session list without manual refresh.

## Cross-Session Persistence

When the application terminates, **`Storage::Manager::save()`** writes each **`Storage::Account`** to `accounts/*.account` files within the application data directory. On subsequent launches, the manager reads these files, recreates **`Main::Account`** instances, and restores the previously active account identifier from **`Core::Settings::lastActiveAccount`**. This mechanism ensures the tray accounts menu reflects the exact same state as the previous session, allowing instant switching without re-authentication.

## Code Examples

Switch to a different account programmatically, mirroring tray click behavior:

```cpp
auto accountId = PeerId(accountUserId);           // PeerId of the target account
Main::Session::instance().switchTo(accountId);   // updates UI, network, etc.

```

Programmatically add a new account equivalent to the tray menu action:

```cpp
Main::Account::addNew(
    Core::App::instance().settings().defaultAuthParameters(),
    Core::App::instance().uiShowRequests());

```

Log out the currently active account:

```cpp
auto *current = Main::Session::instance().current();
if (current) {
    current->logout();      // clears storage, removes from session list
}

```

## Summary

- **Telegram Desktop** maintains separate **`Main::Account`** instances for each MTProto session, stored in a flat_map within **`Main::Session`**.
- **Persistent state** is managed by **`Storage::Account`** and serialized to `accounts/*.account` files via **`Storage::Manager`**.
- The **tray accounts menu** in [`tray_accounts_menu.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/tray_accounts_menu.cpp) renders active sessions and handles switching through **`Main::Session::switchTo()`**.
- **Account limits** enforced by `limits::accounts` prevent exceeding maximum session counts.
- **Automatic synchronization** occurs through `accountsChanged` and `sessionChanged` signals, keeping the tray menu consistent with runtime state.

## Frequently Asked Questions

### How does Telegram Desktop isolate authentication data between accounts?

Each account maintains a distinct **`MTP::AuthKey`** and **`MTP::Instance`** within its **`Main::Account`** object. The **`Storage::Account`** layer serializes these credentials to separate files on disk, ensuring cryptographic isolation. No authentication data is shared between accounts in memory or in persistent storage.

### What is the maximum number of accounts supported in the tray menu?

The application enforces a hard limit defined by **`limits::accounts`**, checked before allowing new account creation. When this limit is reached, the "Add Account" option in the tray menu becomes disabled, and programmatic calls to `addNew()` will fail silently or return an error depending on the caller.

### How does the UI update when switching accounts via the system tray?

Clicking an account in the tray menu triggers **`Main::Session::switchTo()`**, which updates the internal current account pointer and emits **`sessionChanged()`**. This signal propagates to all UI components, causing immediate refreshes of the window title, chat list, notification settings, and message history without requiring an application restart.

### Where is multi-account session data stored between application launches?

Account data persists in the **`accounts/`** subdirectory of the application data folder, with each account represented by a `*.account` file managed by **`Storage::Manager`**. The last active account ID is saved in **`Core::Settings::lastActiveAccount`**, enabling the application to restore the previous context on startup.