# How the Telegram Desktop Admin Log Feature Works: Inside history_admin_log

> Discover how the Telegram Desktop admin log feature uses history_admin_log to convert TL audit events into chat messages. Learn about data fetching, filtering, and rendering.

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

---

**The admin log feature converts administrative audit events from Telegram Layer (TL) objects into standard chat messages using the `history_admin_log` subsystem, which orchestrates data fetching, filtering, and rendering through three specialized layers.**

The admin log provides supergroup administrators with a searchable, filterable history of management actions. In the `telegramdesktop/tdesktop` repository, this functionality resides entirely within `Telegram/SourceFiles/history/admin_log/` and centers on the `history_admin_log` implementation. The architecture separates concerns into distinct UI, logic, and item-generation layers while leveraging the existing message rendering pipeline to display audit entries.

## Architecture of the Admin Log System

The implementation follows a three-layer architecture that isolates presentation from data handling.

- **UI Container (`AdminLog::Widget`)**: Manages the window section, top navigation bar, and search interface. Defined in [`history_admin_log_section.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_admin_log_section.cpp).
- **Logic Layer (`AdminLog::InnerWidget`)**: Handles TL request construction, pagination, filtering, and scroll state. Implemented in [`history_admin_log_inner.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_admin_log_inner.cpp).
- **Item Generation (`GenerateItems`)**: Transforms `MTPChannelAdminLogEvent` objects into standard `HistoryItem` instances. Located in [`history_admin_log_item.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_admin_log_item.cpp).

## Initializing the Admin Log View

When a user navigates to **Settings ▸ Recent actions** in a supergroup, the application constructs an `AdminLog::Widget` instance. The constructor initializes a `FixedBar` (the blue navigation header) and an `InnerWidget` that contains the scrollable log list.

```cpp
_fixedBar = object_ptr<FixedBar>(this, controller, channel);
_inner   = _scroll->setOwnedWidget(
            object_ptr<InnerWidget>(this, controller, channel));

```

Source: [`history_admin_log_section.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_admin_log_section.cpp) (lines 87–102)

The `FixedBar` exposes two reactive streams: `searchRequests()` emits user queries, while `searchCancelRequests()` signals search dismissal. The `Widget` forwards these to the `InnerWidget`:

```cpp
_fixedBar->searchCancelRequests() | rpl::on_next([=] { setInnerFocus(); });
_fixedBar->searchRequests()    | rpl::on_next([=](const QString &q) {
    _inner->applySearch(q);
});

```

Source: [`history_admin_log_section.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_admin_log_section.cpp) (lines 101–108)

## Loading the Administrator List

Before displaying filter options, the system must identify eligible administrators. The `InnerWidget::requestAdmins()` method dispatches a `channels.getParticipants` request with the `channelParticipantsAdmins` filter.

```cpp
_api.request(MTPchannels_GetParticipants(
    _channel->inputChannel(),
    MTP_channelParticipantsAdmins(),
    MTP_int(offset),
    MTP_int(kMaxChannelAdmins),
    MTP_long(participantsHash)
))
.done([=](const MTPchannels_ChannelParticipants &result) { … })
.send();

```

Source: [`history_admin_log_inner.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_admin_log_inner.cpp) (lines 70–80)

Upon receiving the response, the method extracts `UserData` objects into `_admins` and `_adminsCanEdit` vectors, then calls `showFilter()` to populate the filter UI if the user had requested it.

## Fetching Admin Log Events

### Trigger Points

The system initiates log loading in three scenarios:

1. **Initial load**: `Widget::showEvent` triggers `preloadMore(Direction::Up)`.
2. **Scroll proximity**: `InnerWidget::checkPreloadMore()` monitors `_visibleTop` and `_visibleBottom`, calling `preloadMore()` when the viewport nears content boundaries.
3. **Filter or search changes**: `applyFilter()` and `applySearch()` set `_filterChanged = true` and invoke `clearAndRequestLog()`, resetting pagination and triggering upward preloading.

### Building the TL Request

The `InnerWidget::preloadMore(Direction direction)` method constructs the `channels.getAdminLog` request by assembling bitmask flags from `FilterValue::flags` and optional administrator vectors.

```cpp
auto flags = MTPchannels_GetAdminLog::Flags(0);
auto filter = [&] {
    using Flag = MTPDchannelAdminLogEventsFilter::Flag;
    using LocalFlag = FilterValue::Flag;
    const auto f = _filter.flags.value_or(LocalFlag());
    return empty
        | ((f & LocalFlag::Join)    ? Flag::f_join    : empty)
        | ((f & LocalFlag::Leave)   ? Flag::f_leave   : empty)
        … // additional flags omitted for brevity
}();

QVector<MTPInputUser> admins;
if (_filter.admins && !_filter.admins->empty()) {
    for (const auto &admin : *_filter.admins) {
        admins.push_back(admin->inputUser());
    }
    flags |= MTPchannels_GetAdminLog::Flag::f_admins;
}

auto maxId = (direction == Direction::Up) ? _minId : 0;
auto minId = (direction == Direction::Up) ? 0 : _maxId;
auto perPage = _items.empty() ? kEventsFirstPage : kEventsPerPage;

```

Source: [`history_admin_log_inner.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_admin_log_inner.cpp) (lines 45–88)

The final request includes the channel identifier, search query string, event filter, administrator whitelist, and pagination cursors:

```cpp
requestId = _api.request(MTPchannels_GetAdminLog(
    MTP_flags(flags),
    _channel->inputChannel(),
    MTP_string(_searchQuery),
    MTP_channelAdminLogEventsFilter(MTP_flags(filter)),
    MTP_vector<MTPInputUser>(admins),
    MTP_long(maxId),
    MTP_long(minId),
    MTP_int(perPage)
))
.done([=, &requestId, &loadedFlag](const MTPchannels_AdminLogResults &result) { … })
.send();

```

Source: [`history_admin_log_inner.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_admin_log_inner.cpp) (lines 88–98)

### Handling the Response

The response callback processes `MTPchannels_AdminLogResults` by updating local user and chat caches, then appending events to the view:

```cpp
auto &results = result.c_channels_adminLogResults();
_channel->owner().processUsers(results.vusers());
_channel->owner().processChats(results.vchats());
if (!loadedFlag) {
    addEvents(direction, results.vevents().v);
}

```

Source: [`history_admin_log_inner.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_admin_log_inner.cpp) (lines 98–104)

## Converting TL Events to Renderable Items

The `InnerWidget::addEvents()` method iterates over the `MTPChannelAdminLogEvent` vector and delegates item construction to `GenerateItems` in [`history_admin_log_item.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_admin_log_item.cpp).

```cpp
GenerateItems(this, _history, data, addOne);

```

Source: [`history_admin_log_inner.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_admin_log_inner.cpp) (lines 70–77)

The generator function examines the event's action type (such as `channelAdminLogEventActionChangeTitle` or `channelAdminLogEventActionKick`) and constructs a standard `HistoryItem` through these steps:

1. **Extract metadata**: Retrieve original sent dates and real message IDs using `ExtractSentDate` and `ExtractRealMsgId`.
2. **Prepare message**: Create an `MTPMessage` surrogate via `PrepareLogMessage` that mimics regular messages while stripping unnecessary flags.
3. **Instantiate item**: Build a `HistoryItem` through `history->addNewMessage` and wrap it in an `OwnedItem`.
4. **Store auxiliary data**: Populate `_itemDates` for floating date headers, `_eventIds` for deduplication, and `_realIdsForReport` for report actions.

## Rendering the Log

`InnerWidget` inherits from `Ui::RpWidget` and implements the **HistoryView** delegate interface, allowing reuse of the standard chat rendering pipeline:

```cpp
void InnerWidget::visibleTopBottomUpdated(int visibleTop, int visibleBottom) override;
void InnerWidget::paintEvent(QPaintEvent *e) override;

```

Source: [`history_admin_log_inner.h`](https://github.com/telegramdesktop/tdesktop/blob/main/history_admin_log_inner.h) (lines 61–70)

The scroll area (`Ui::ScrollArea`) provides viewport coordinates. When `visibleTopBottomUpdated` fires, the method iterates through `_items`, updates visibility ranges via `view->item()->setVisibleTopBottom(...)`, and triggers additional preloading when necessary. Because items are standard `HistoryView::Element` subclasses (such as `HistoryMessage` or `HistoryServiceMsg`), they automatically support link handling, selection, and context menus without custom rendering code.

## Filtering and Searching

The admin log supports both event-type filtering and text search.

- **Filtering**: The `showFilter()` method presents a dialog populated from the `_admins` vector and action-type checkboxes. Upon confirmation, `applyFilter(FilterValue&&)` updates `_filter.flags` and `_filter.admins`, then calls `clearAndRequestLog()`.
- **Searching**: The top bar emits queries through `FixedBar::searchRequests()`. `InnerWidget::applySearch()` stores the string in `_searchQuery` and triggers `clearAndRequestLog()`, which reissues the `channels.getAdminLog` request with the updated parameters.

Both operations reset pagination and reload events from the server using the new criteria.

## Pagination and State Preservation

The system maintains separate request identifiers for bidirectional loading: `_preloadUpRequestId` for older events and `_preloadDownRequestId` for newer ones. Boolean flags `_upLoaded` and `_downLoaded` track when pagination reaches log boundaries.

When users navigate away from the admin log, `Widget::createMemento()` captures the current state via `InnerWidget::saveState()`, preserving scroll position, loaded items, filter configuration, and search query. `InnerWidget::restoreState()` reconstructs this state upon return, enabling instant back-navigation without refetching data.

## Summary

- The **admin log feature** consists of three layers: the section widget (`Widget`), the logic handler (`InnerWidget`), and the item generator (`GenerateItems`).
- **Administrator lists** are fetched via `channels.getParticipants` before filter UI presentation.
- **Log events** are retrieved through `channels.getAdminLog` with bitmask filters, optional admin whitelists, and pagination cursors managed in `preloadMore()`.
- **TL events** are converted to standard `HistoryItem` objects in [`history_admin_log_item.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/history_admin_log_item.cpp), enabling reuse of the generic chat view.
- **Filtering and search** trigger full reloads with updated parameters, while **mementos** preserve state for seamless navigation.

## Frequently Asked Questions

### How does the admin log handle pagination for large audit histories?

The `InnerWidget` maintains separate request IDs for upward and downward loading (`_preloadUpRequestId` and `_preloadDownRequestId`). It calculates `maxId` and `minId` cursors based on the loading direction, requesting `kEventsPerPage` entries per batch. When scrolling near content boundaries, `checkPreloadMore()` automatically triggers additional fetches until `_upLoaded` or `_downLoaded` flags indicate boundary conditions.

### Can the admin log display events from specific administrators only?

Yes. When the filter UI includes specific administrators, `preloadMore()` constructs a `QVector<MTPInputUser>` from the selected admins and sets the `MTPchannels_GetAdminLog::Flag::f_admins` flag. The server returns only events performed by the specified users.

### Why does the admin log use standard HistoryItem objects instead of custom views?

The implementation reuses `HistoryView::Element` subclasses to ensure visual consistency with regular chat messages and to inherit existing functionality like text selection, link handling, and context menus. The `GenerateItems` function creates these standard items by transforming `MTPChannelAdminLogEvent` data into compatible `MTPMessage` structures, avoiding duplicate rendering logic.

### Where is the search query processed in the admin log pipeline?

The search query originates in `FixedBar` and propagates to `InnerWidget::applySearch()`, which stores the string in `_searchQuery`. This value is passed as `MTP_string(_searchQuery)` to the `channels.getAdminLog` request. The Telegram servers perform the text search, returning only matching events to the client.