# How Telegram Mini Apps Are Implemented in Telegram iOS: WebUI Module Deep Dive

> Explore how Telegram Mini Apps are implemented in Telegram iOS. Discover the WebUI module's WebAppWebView, JavaScript bridge, and WebAppController for seamless native and web communication.

- Repository: [TelegramMessenger/Telegram-iOS](https://github.com/TelegramMessenger/Telegram-iOS)
- Tags: deep-dive
- Published: 2026-04-07

---

**Telegram Mini Apps run inside a custom WKWebView wrapper (`WebAppWebView`) that injects a JavaScript bridge (`TelegramWebviewProxy`) to enable bidirectional communication between web content and native iOS code, orchestrated by the `WebAppController` in the WebUI submodule.**

The implementation of Telegram Mini Apps (Web Apps) in the TelegramMessenger/Telegram-iOS repository relies on a dedicated WebUI submodule that orchestrates the rendering, communication, and lifecycle of web-based bot applications. This architecture allows Mini Apps to run securely inside the native client while maintaining seamless interaction with Telegram's native UI components and server infrastructure.

## Architecture Overview: The Mini App Launch Sequence

When a user initiates a Mini App from a chat interface, the system executes a structured flow across multiple layers:

1. The **Chat UI** invokes `openWebAppImpl` located in [`submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift) to process the request and validate bot permissions.
2. The function queries Telegram's servers via `engine.messages.requestWebView` (or `requestAppWebView` for internal links) to retrieve a signed HTTPS URL and unique `queryId`.
3. A `WebAppParameters` struct encapsulates all context—including peer IDs, bot information, URL, payload, and display options—to instantiate the `WebAppController`.
4. Inside the controller, a specialized `WebAppWebView` loads the signed URL while injecting the JavaScript bridge and configuring security policies.
5. Bidirectional communication flows through `WKScriptMessageHandler`, allowing the web page to send events like button presses to native code and vice versa via `evaluateJavaScript`.

## Entry Point: From Chat Interface to Controller

The primary entry point for launching Mini Apps is the `openWebAppImpl` function in [`ChatControllerOpenWebApp.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/ChatControllerOpenWebApp.swift). This function determines whether to display Terms of Service screens, validates that the bot is whitelisted, and constructs the `WebAppParameters` struct that carries all necessary launch data.

According to the source code, `openWebAppImpl` prepares the parameters (lines 18‑33, 70‑84, and 119‑130) and eventually calls `standaloneWebAppController`, which creates and presents the `WebAppController` instance. The controller initialization logic resides in [`submodules/WebUI/Sources/WebAppController.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/submodules/WebUI/Sources/WebAppController.swift) (lines 50‑84), where the struct packages data including the source peer, fullscreen flags, theme settings, and the `keepAliveSignal` observer.

## WebAppWebView: Custom WKWebView Configuration

The `WebAppWebView` class, defined in [`submodules/WebUI/Sources/WebAppWebView.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/submodules/WebUI/Sources/WebAppWebView.swift), extends `WKWebView` to provide a customized, isolated browsing environment for Mini Apps. Key implementation details include:

- **Per-Account Data Isolation**: On iOS 17+, the implementation initializes a unique `WKWebsiteDataStore` identified by a UUID stored in `UserDefaults`, ensuring cookies and local storage remain isolated per Telegram account rather than shared system-wide.
- **JavaScript Injection**: The view injects three critical scripts at document start (lines 8‑46):
  - `eventProxySource`: Creates the global `TelegramWebviewProxy` object that exposes `postEvent(name, data)` to web pages, mapping to `window.webkit.messageHandlers.performAction.postMessage`.
  - `selectionSource`: Disables user text selection except within input fields to maintain native app feel.
  - `videoSource`: Forces inline video playback and prevents `webkitEnterFullscreen` calls to keep video within the Mini App container.
- **Message Handling**: A `WeakGameScriptMessageHandler` registers for the `performAction` channel, routing messages from JavaScript to the controller's `handleScriptMessage` method (lines 172‑179) without creating retain cycles.

## Bridging Native and JavaScript Code

Communication between the Mini App's web content and native iOS code occurs through a dedicated bridge mechanism. The JavaScript side uses `window.TelegramWebviewProxy.postEvent(name, data)`, which the injected script translates to `window.webkit.messageHandlers.performAction.postMessage`.

On the native side, `WebAppController` implements `WKScriptMessageHandler` to receive these messages in `handleScriptMessage` (lines 170‑200). The controller parses event names—such as `main_button_pressed`, `secondary_button_pressed`, `viewport_changed`, or `content_safe_area_changed`—and updates the native UI accordingly, including navigation bar visibility, button states, and layout insets.

For native-to-web communication, the controller calls `webView.sendEvent(name:data:)`, which evaluates JavaScript to invoke `window.TelegramGameProxy.receiveEvent(...)`. This allows the native layer to push updates like theme changes, header color modifications, or viewport adjustments to the web content immediately.

## UI Components and Layout Management

The WebUI module includes specialized components for Mini App interfaces:

- **WebAppTitleView**: Located in [`submodules/WebUI/Sources/WebAppTitleView.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/submodules/WebUI/Sources/WebAppTitleView.swift), this component renders the Mini App title, verification badges using `EmojiStatusComponent`, and subtitle counters (e.g., "Mini-app").
- **Button Controls**: Separate node-based implementations handle the main button, secondary button, and permission requests, defined in auxiliary files like [`WebAppPermissions.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/WebAppPermissions.swift), [`WebAppSecureStorage.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/WebAppSecureStorage.swift), and [`WebAppMessagePreviewScreen.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/WebAppMessagePreviewScreen.swift).
- **Safe Area Handling**: When layout changes occur—such as toggling fullscreen mode or keyboard appearance—`WebAppController.containerLayoutUpdated` recalculates insets and dispatches `content_safe_area_changed` events to the web view via the JavaScript bridge to ensure web content respects device safe areas.

## Server Communication and Lifecycle Management

Before the web view loads, the system establishes a server session. The `requestWebView` method returns not only the signed URL but also a `keepAliveSignal`. This observable signal monitors the server state; if the bot removes the keyboard or terminates the session, the signal completes and triggers automatic closure of the Mini App controller via `WebAppController.dismiss()`.

The signed URL generated by the server includes the bot's `app_id`, authentication hash, and optional payload parameters, ensuring secure verification of the Mini App's identity before rendering begins.

## Code Example: Launching a Mini App from Chat

The following simplified Swift code illustrates how the chat interface initiates a Mini App session through the WebUI module:

```swift
func openMiniApp(
    context: AccountContext,
    chatController: ViewController,
    botPeer: EnginePeer,
    url: String,
    buttonText: String,
    source: ChatOpenWebViewSource
) {
    // The heavy lifting is done inside openWebAppImpl
    openWebAppImpl(
        context: context,
        parentController: chatController,
        updatedPresentationData: nil,
        botPeer: botPeer,
        chatPeer: nil,
        threadId: nil,
        buttonText: buttonText,
        url: url,
        simple: false,
        source: source,
        skipTermsOfService: false,
        payload: nil,
        verifyAgeCompletion: nil
    )
}

```

This function chain ultimately instantiates the `WebAppController` with a fully configured `WebAppWebView`, establishing the complete Mini App runtime environment as implemented in the Telegram iOS WebUI submodule.

## Summary

- **Entry Architecture**: Telegram Mini Apps launch through `openWebAppImpl` in [`ChatControllerOpenWebApp.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/ChatControllerOpenWebApp.swift), which prepares `WebAppParameters` and retrieves signed URLs from Telegram servers via `engine.messages.requestWebView`.
- **WebView Implementation**: The `WebAppWebView` class in [`WebAppWebView.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/WebAppWebView.swift) configures per-account `WKWebsiteDataStore` isolation (iOS 17+) and injects the `TelegramWebviewProxy` JavaScript bridge via `eventProxySource`.
- **Bidirectional Bridge**: Native and web layers communicate via `performAction` message handlers and `evaluateJavaScript`, supporting events like `main_button_pressed` and `viewport_changed` through `handleScriptMessage`.
- **UI Integration**: The `WebAppController` manages native UI elements including titles (`WebAppTitleView`), buttons, permissions, and safe areas while coordinating layout updates with the web content.
- **Lifecycle Control**: Server-side `keepAliveSignal` monitoring ensures Mini Apps close automatically when bots terminate sessions, with cleanup handled in `WebAppController.dismiss()`.

## Frequently Asked Questions

### What is the role of WebAppController in Telegram Mini Apps?

The `WebAppController` serves as the primary view controller hosting Mini Apps in the Telegram iOS client. Implemented in [`submodules/WebUI/Sources/WebAppController.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/submodules/WebUI/Sources/WebAppController.swift), it manages the navigation bar, fullscreen transitions, download handling via `WKDownloadDelegate`, and bridges communication between the `WebAppWebView` and the native application layer through `handleScriptMessage`.

### How does the JavaScript bridge work in Telegram Mini Apps?

The bridge relies on a `TelegramWebviewProxy` object injected into the web view's JavaScript context by `WebAppWebView` (lines 39‑44). When the Mini App calls methods on this object, it posts messages to the `performAction` handler registered with `WKScriptMessageHandler`. The controller's `handleScriptMessage` method receives these events and updates native UI or forwards data to Telegram's servers accordingly.

### Why does Telegram Mini Apps use a custom WKWebView configuration?

The `WebAppWebView` uses a custom configuration to enforce security and isolation policies. On iOS 17+, it creates per-account `WKWebsiteDataStore` instances to separate cookies and storage between different Telegram accounts. It also injects scripts to disable text selection, force inline video playback, and prevent unwanted fullscreen behaviors, ensuring consistent Mini App behavior across different web content.

### Where does the Mini App URL come from when opening a Web App?

The URL is generated server-side through `engine.messages.requestWebView` or `requestAppWebView` calls made in `openWebAppImpl`. The Telegram server returns a signed HTTPS URL containing the bot's `app_id`, authentication hash, and optional payload, which the `WebAppWebView` loads to render the Mini App content securely within the WebUI module.