# How Telegram Desktop Processes Deep Links: A Complete Technical Guide

> Explore how Telegram Desktop processes deep links. Learn about the centralized router, URL parsing, and handler dispatching in this technical guide. Discover the core components behind TDesktop deep link functionality.

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

---

**Telegram Desktop routes deep links through a centralized `DeepLinks::Router` that parses URLs into section/path/context pairs and dispatches them to registered handlers in `core/deep_links/` modules.**

The `telegramdesktop/tdesktop` repository implements a modular deep link processing pipeline that converts raw `tg://` URLs into concrete application actions. This system handles everything from settings screens to bot interactions through a lightweight router architecture defined in the `core/deep_links/` directory.

## Entry Points: How Deep Links Enter the Application

Deep links reach the application through three primary channels, all converging on a single routing function.

When the OS launches Telegram with a `tg://` URL scheme, `Core::Application::handleUrl` (defined in [`main/main_app_config.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/main/main_app_config.cpp)) forwards the command to `TryRouterForLocalUrl`. This same function handles in-app links (such as `t.me/...` or internal `tg://resolve?...` URLs) parsed by `Core::LocalUrlHandlers`. Internal media URLs like `tg://open?url=…` also flow through this pipeline.

The common entry point is **`TryRouterForLocalUrl`** in [`core/local_url_handlers.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/core/local_url_handlers.cpp):

```cpp
bool TryRouterForLocalUrl(
        Window::SessionController *controller,
        const QString &command) {
    return DeepLinks::Router::Instance().tryHandle(controller, command);
}

```

## Parsing and Context Creation

Before routing occurs, the raw command string undergoes structured parsing. The `ParseCommand` function in [`core/deep_links/deep_links_router.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/core/deep_links/deep_links_router.cpp) splits the URL into **section**, **path**, and **query parameters**, stripping leading slashes and separating the query string into a key-value map.

The resulting data populates the `Context` struct defined in [`core/deep_links/deep_links_types.h`](https://github.com/telegramdesktop/tdesktop/blob/main/core/deep_links/deep_links_types.h):

```cpp
struct Context {
    Window::SessionController *controller = nullptr;
    QString section;
    QString path;
    QMap<QString, QString> params;
};

```

This context object encapsulates everything the router needs: the active session controller, the target section (e.g., `"settings"`), the specific path (e.g., `"privacy"`), and any query parameters passed by the link.

## The Router Architecture

`Core::DeepLinks::Router` maintains a map of `section → list<Entry>`, where each **Entry** couples a path pattern with an **Action** variant and behavioral flags.

The `Entry` struct (defined in the router headers) contains:

```cpp
struct Entry {
    QString path;
    Action action;
    bool requiresAuth = true;
    bool skipActivation = false;
};

```

The **Action** type is a `std::variant` supporting:
- **SettingsSection** – opens a specific settings page
- **SettingsControl** – highlights a UI control within settings
- **CodeBlock** – executes a custom lambda handler
- **AliasTo** – forwards to another section/path combination

Processing occurs in three stages:
1. **`dispatch`** – locates the matching `Entry` for the supplied section/path pair
2. **`executeAction`** – invokes the specific action (opening UI, running code, or redirecting)
3. **Result handling** – returns `Result::Handled`, `Result::NeedsAuth`, `Result::NotFound`, or `Result::Unsupported`

If the result is `Handled` and the entry does not set `skipActivation`, the router automatically brings the application window to the foreground.

## Registering Deep Link Handlers

Handlers register during application startup through module-specific `Register…Handlers` functions called from the router’s constructor.

### Settings Deep Links

The `RegisterSettingsHandlers` function in [`core/deep_links/deep_links_settings.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/core/deep_links/deep_links_settings.cpp) declares all settings-related routes:

```cpp
void RegisterSettingsHandlers(Router &router) {
    router.add(u"settings"_q, {
        .path = u"privacy"_q,
        .action = SettingsSection{ ::Settings::PrivacySecurityId() },
    });
    router.add(u"settings"_q, {
        .path = u"language"_q,
        .action = CodeBlock{ [](const Context &ctx) {
            return ShowLanguageBox(ctx);
        }},
        .requiresAuth = false,
    });
}

```

This registration pattern allows `tg://settings/privacy` to open the Privacy & Security screen, while `tg://settings/language` executes the `ShowLanguageBox` lambda without requiring authentication.

### Other Handler Modules

- **Contacts** – `RegisterContactsHandlers` in [`core/deep_links/deep_links_contacts.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/core/deep_links/deep_links_contacts.cpp) handles `tg://addcontact?...` and related patterns
- **Chats** – `RegisterChatsHandlers` in [`core/deep_links/deep_links_chats.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/core/deep_links/deep_links_chats.cpp) processes `tg://resolve?...` for usernames, phone numbers, and bot start parameters
- **New** – `RegisterNewHandlers` in [`core/deep_links/deep_links_new.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/core/deep_links/deep_links_new.cpp) contains experimental or future-proof link handlers

## Complete Processing Flow Example

Consider the user clicking `tg://settings/language?lang=es`:

1. The OS launches Telegram with the URL string as an argument
2. `Application::handleUrl` calls `TryRouterForLocalUrl(controller, "tg://settings/language?lang=es")`
3. `Router::tryHandle` invokes `ParseCommand`, producing `Context{ section="settings", path="language", params={"lang":"es"} }`
4. `dispatch` queries the section map for `"settings"`, finding the entry with `path="language"`
5. `executeAction` runs the registered `CodeBlock` which calls `ShowLanguageBox(ctx)`
6. The language picker UI opens, potentially pre-highlighting Spanish based on the query parameter
7. The router returns `Result::Handled` and activates the main window

If the link targeted `tg://settings/privacy` while the user was logged out, the router would return `Result::NeedsAuth` and the action would not execute.

## Internal vs. External URL Handlers

When the deep link router fails to find a matching section, control falls through to internal handlers defined in [`core/local_url_handlers.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/core/local_url_handlers.cpp). These handle specialized URL patterns not routed through the standard pipeline:

- `^media_timestamp/?\?base=…&t=(\d+)` – `OpenMediaTimestamp` jumps to specific timestamps in media files
- `^url:(.+)$` – `OpenExternalLink` forwards to the system default browser
- `^copy:(.+)$` – `CopyPeerId` copies numeric peer IDs to the clipboard

These patterns are evaluated after the router returns `NotFound`, ensuring all `tg://` URLs receive appropriate handling.

## Summary

- **Entry Point**: All deep links converge on `TryRouterForLocalUrl` in [`core/local_url_handlers.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/core/local_url_handlers.cpp), which delegates to `DeepLinks::Router::Instance().tryHandle()`
- **Parsing**: `ParseCommand` in [`core/deep_links/deep_links_router.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/core/deep_links/deep_links_router.cpp) decomposes URLs into `Context` objects containing section, path, and parameters
- **Architecture**: The router maintains a section-indexed map of `Entry` structs containing `Action` variants (SettingsSection, CodeBlock, etc.) and flags for authentication requirements
- **Registration**: Module-specific files ([`deep_links_settings.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/deep_links_settings.cpp), [`deep_links_chats.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/deep_links_chats.cpp), etc.) register handlers during router construction
- **Results**: The router returns distinct results (Handled, NeedsAuth, NotFound) and manages window activation based on `skipActivation` flags
- **Fallback**: Non-router patterns are handled by internal URL handlers in the same local URL handlers file

## Frequently Asked Questions

### What is the primary entry point for deep link processing in Telegram Desktop?

The primary entry point is the `TryRouterForLocalUrl` function located in [`Telegram/SourceFiles/core/local_url_handlers.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/Telegram/SourceFiles/core/local_url_handlers.cpp). This function is called by `Core::Application::handleUrl` when the OS launches the app with a `tg://` URL, and by `Core::LocalUrlHandlers` when processing in-app links. It delegates to `DeepLinks::Router::Instance().tryHandle()` to perform the actual routing.

### How does the router determine which handler to execute?

The router uses a two-level lookup system. First, it extracts the **section** from the parsed URL (the component immediately following `tg://`). It then searches the list of `Entry` objects registered for that section, matching the **path** component against each entry's `path` string. The first matching entry's `Action` variant (SettingsSection, CodeBlock, etc.) is then executed via `executeAction`.

### Can deep links work when the user is not authenticated?

Yes, but only for handlers explicitly registered with `requiresAuth = false`. For example, the language settings handler in [`deep_links_settings.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/deep_links_settings.cpp) sets this flag to allow `tg://settings/language` to function before login. Links targeting protected areas like `tg://settings/privacy` return `Result::NeedsAuth` when no session exists, effectively queuing or ignoring the request until authentication completes.

### Where are new deep link handlers added in the codebase?

New handlers are added in the module-specific registration files within `Telegram/SourceFiles/core/deep_links/`. Settings handlers belong in [`deep_links_settings.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/deep_links_settings.cpp), contact-related handlers in [`deep_links_contacts.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/deep_links_contacts.cpp), chat and username resolution in [`deep_links_chats.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/deep_links_chats.cpp), and experimental features in [`deep_links_new.cpp`](https://github.com/telegramdesktop/tdesktop/blob/main/deep_links_new.cpp). Each file exports a `Register…Handlers` function that populates the router's section map during application initialization.