# How Telegram Desktop Manages and Stores Stickers, Masks, and Custom Emoji Locally

> Discover how Telegram Desktop locally manages and stores stickers, masks, and custom emoji. Learn about the Data::Stickers manager, CustomEmojiManager, and file caching strategies.

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

---

**Telegram Desktop uses a centralized `Data::Stickers` manager for sticker sets and a dedicated `CustomEmojiManager` for emoji, persisting data in binary cache files under `tdata/stickers` and media files under `tdata/files` with flag-based differentiation for masks.**

The **telegramdesktop/tdesktop** codebase treats stickers, masks, and custom emoji as distinct media families within a unified subsystem. While regular stickers, masks, and emoji packs share common infrastructure, each type follows specific persistence rules and storage strategies optimized for their usage patterns in the Qt-based client.

## The Stickers Subsystem Architecture

The architecture relies on three primary components defined in `Telegram/SourceFiles/data/stickers/`.

**`Data::Stickers`** acts as the central manager tracking installation state, ordering, and update signals for all sticker sets. **`StickersSet`** represents individual sets, storing metadata, `DocumentData*` pointers to covers, and set flags including the `Masks` identifier. **`CustomEmojiManager`** handles emoji resolution, frame caching, and UI object creation.

The manager distinguishes three `StickersType` values declared in [`data_stickers.h`](https://github.com/telegramdesktop/tdesktop/blob/main/data_stickers.h):
- `Stickers` – standard animated and static packs
- `Masks` – face-painting mask packs
- `Emoji` – collectible emoji packs

## How Sticker Masks Are Managed and Stored

### Flag-Based Identification

Mask sets are identified by the `StickersSetFlag::Masks` bit set during parsing from MTProto `DstickerSet` objects. The flag extraction occurs in [`data_stickers_set.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/data_stickers_set.cpp):

```cpp
[[nodiscard]] StickersSetFlags ParseStickersSetFlags(
    const MTPDstickerSet &data);

```

This function parses server responses to determine if a set should be categorized as masks rather than standard stickers.

### Separate Ordering and Persistence

Unlike regular stickers, masks maintain an independent ordering list stored in `Data::Stickers::_maskSetsOrder`. The `StickersSetsOrder` type (a `QList<uint64>` of set IDs) exposes two accessors in [`data_stickers.h`](https://github.com/telegramdesktop/tdesktop/blob/main/data_stickers.h):

```cpp
[[nodiscard]] const StickersSetsOrder &maskSetsOrder() const;
[[nodiscard]] StickersSetsOrder &maskSetsOrderRef();

```

When users install, remove, or reorder masks, the manager updates this list and persists the entire state via `Data::Stickers::save()` in [`data_stickers.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/data_stickers.cpp). This writes a binary blob using `QDataStream` to the local `tdata/stickers` cache file.

### UI Integration

UI components query `maskSetsOrder()` to build mask-specific interfaces. The `StickersBox::Inner::rebuild(bool masks)` method in [`boxes/stickers_box.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/boxes/stickers_box.cpp) (line 2308) uses this list to populate the Masks tab, while `StickersListWidget::populate()` in [`chat_helpers/stickers_list_widget.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/chat_helpers/stickers_list_widget.cpp) (lines 3155-3161) renders the mask grid based on this persisted order.

## How Custom Emoji Are Managed and Stored

### Public API and Factory Methods

The `CustomEmojiManager` class in [`data_custom_emoji.h`](https://github.com/telegramdesktop/tdesktop/blob/main/data_custom_emoji.h) provides the primary interface for emoji creation. Key entry points include:

- `factory(SizeTag tag, int sizeOverride)` – Creates a `Ui::Text::CustomEmojiFactory` for text renderers
- `create(DocumentId id, ...)` – Returns `std::unique_ptr<Ui::Text::CustomEmoji>` instances

These methods handle document resolution and size-specific rendering preparation.

### Instance Caching and Loading

`CustomEmojiManager` maintains per-size caches for performance:

```cpp
std::array<std::unordered_map<DocumentId, std::unique_ptr<Ui::CustomEmoji::Instance>>,
           kSizeCount> _instances;
std::array<base::flat_map<DocumentId,
           std::vector<base::weak_ptr<CustomEmojiLoader>>>,
           kSizeCount> _loaders;

```

**Instances** cache rendered frames (PNG or Lottie) for specific documents and size tags, while **Loaders** manage asynchronous fetching from the network or local file cache. When requesting an emoji, the manager returns cached instances or creates a `CustomEmojiLoader` that downloads the underlying `DocumentData` via the generic file-download subsystem ([`storage/file_download.h`](https://github.com/telegramdesktop/tdesktop/blob/main/storage/file_download.h)).

### Serialization and Local Persistence

Custom emoji IDs serialize into strings embeddable in TL messages:

```cpp
[[nodiscard]] QString SerializeCustomEmojiId(DocumentId id);
[[nodiscard]] QString SerializeCustomEmojiId(not_null<DocumentData*> document);

```

Deserialization uses:

```cpp
[[nodiscard]] DocumentId ParseCustomEmojiData(QStringView data);

```

These strings persist in the local database within `Data::Message` entities, reactions, and peer icons. The actual image data resides in `Data::DocumentData`, downloaded via `api().request(MTPmessages_GetDocument(...))` and stored in `tdata/files`. The `CustomEmojiLoader` checks `cacheKey(document)` before initiating network requests.

### Reactive UI Rendering

The manager implements reactive patterns using `rpl::producer<>` callbacks. It emits `repaintLater()` requests batched by a timer (`_repaintTimer`), allowing UI widgets in [`history_view_custom_emoji.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_view_custom_emoji.cpp) and [`ui/text/text_custom_emoji.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/ui/text/text_custom_emoji.cpp) to update frames asynchronously without blocking the main thread.

## Local Storage Layout and File Structure

Telegram Desktop organizes sticker and emoji data across specific directories:

- **`tdata/files`** – Contains downloaded sticker documents, mask thumbnails, and custom emoji media (PNG/Lottie). Written by `Storage::FileDownload` when `CustomEmojiLoader` or thumbnail requests complete.

- **`tdata/stickers`** – Binary cache storing serialized `Data::Stickers` state including installed set IDs, mask order (`maskSetsOrder`), recent stickers, and saved GIFs. Written by `Data::Stickers::save()`.

- **`tdata/emoji`** – Stores serialized custom emoji ID strings attached to messages, profiles, or forum topics via `SerializeCustomEmojiId`.

- **`tdata/cache`** – Lottie frames and pre-rendered bitmap caches for fast UI painting. Managed by `Ui::CustomEmoji::Loader` using `cacheKey()` logic.

The auto-clear logic in [`storage/storage_account.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/storage/storage_account.cpp) (lines 2436-2680) evicts these caches when users exceed configured storage limits.

## Practical Code Examples

### Creating a Custom Emoji Instance

```cpp
// Assume we have a DocumentId for a custom emoji.
DocumentId emojiId = ...;

// Obtain the manager from the session.
auto &customEmoji = session->data().customEmojiManager();

// Create a UI-ready custom emoji with normal size.
auto custom = customEmoji.create(
    emojiId,
    [] { /* UI should repaint when the emoji loads */ },
    CustomEmojiManager::SizeTag::Normal);

// Use it in a text widget.
auto text = Ui::Text::CustomEmoji(custom.get());
someLabel->setText(text);

```

*Source*: [`data_custom_emoji.h`](https://github.com/telegramdesktop/tdesktop/blob/main/data_custom_emoji.h) (lines 40-55) and [`data_custom_emoji.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/data_custom_emoji.cpp) (creation logic).

### Listing Installed Mask Packs

```cpp
auto &stickers = session->data().stickers();
const auto &maskOrder = stickers.maskSetsOrder(); // QList<uint64> of mask set ids.

for (const uint64 setId : maskOrder) {
    const auto *set = stickers.set(setId);
    if (set && set->type() == StickersType::Masks) {
        qDebug() << "Mask pack:" << set->title;
    }
}

```

*Source*: [`data_stickers.h`](https://github.com/telegramdesktop/tdesktop/blob/main/data_stickers.h) (mask order getters, lines 169-173).

## Summary

- **Masks** are standard sticker sets flagged with `StickersSetFlag::Masks`, stored with a dedicated `maskSetsOrder` list in the binary `tdata/stickers` cache, and rendered by UI components querying `Data::Stickers::maskSetsOrder()`.

- **Custom emoji** use `CustomEmojiManager` with per-size instance caches, document-based loading via `CustomEmojiLoader`, and ID serialization through `SerializeCustomEmojiId()`.

- **Storage** separates metadata (binary caches in `tdata/stickers`) from media (files in `tdata/files`), with automatic eviction managed by [`storage_account.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/storage_account.cpp).

- **Integration** follows reactive patterns where managers emit repaint signals and UI widgets subscribe to updates for asynchronous frame rendering.

## Frequently Asked Questions

### How does Telegram Desktop distinguish between regular stickers and masks?

Telegram Desktop checks the `StickersSetFlag::Masks` bit parsed from MTProto `DstickerSet` objects via `ParseStickersSetFlags()` in [`data_stickers_set.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/data_stickers_set.cpp). Sets with this flag populate the separate `maskSetsOrder` list rather than the standard sticker order list.

### Where are custom emoji files actually stored on disk?

Custom emoji media files reside in `tdata/files` alongside other documents, while their IDs serialize into strings stored in the local message database. Pre-rendered frames cache in `tdata/cache` using keys generated by `cacheKey()` in [`data_custom_emoji.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/data_custom_emoji.cpp).

### What happens when sticker cache storage limits are exceeded?

The auto-clear logic in [`storage/storage_account.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/storage/storage_account.cpp) (lines 2436-2680) automatically evicts files from `tdata/files` and cache entries based on age and size constraints, preserving the binary metadata in `tdata/stickers` but removing the underlying media blobs.

### Can developers access the mask set order programmatically?

Yes, the `Data::Stickers` class exposes `maskSetsOrder()` and `maskSetsOrderRef()` methods in [`data_stickers.h`](https://github.com/telegramdesktop/tdesktop/blob/main/data_stickers.h) (lines 169-173), returning a `QList<uint64>` of mask set IDs that reflects the user's current installation and ordering preferences.