How Telegram Mini Apps Are Implemented in Telegram iOS: WebUI Module Deep Dive
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:
- The Chat UI invokes
openWebAppImpllocated insubmodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swiftto process the request and validate bot permissions. - The function queries Telegram's servers via
engine.messages.requestWebView(orrequestAppWebViewfor internal links) to retrieve a signed HTTPS URL and uniquequeryId. - A
WebAppParametersstruct encapsulates all context—including peer IDs, bot information, URL, payload, and display options—to instantiate theWebAppController. - Inside the controller, a specialized
WebAppWebViewloads the signed URL while injecting the JavaScript bridge and configuring security policies. - Bidirectional communication flows through
WKScriptMessageHandler, allowing the web page to send events like button presses to native code and vice versa viaevaluateJavaScript.
Entry Point: From Chat Interface to Controller
The primary entry point for launching Mini Apps is the openWebAppImpl function in 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 (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, 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
WKWebsiteDataStoreidentified by a UUID stored inUserDefaults, 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 globalTelegramWebviewProxyobject that exposespostEvent(name, data)to web pages, mapping towindow.webkit.messageHandlers.performAction.postMessage.selectionSource: Disables user text selection except within input fields to maintain native app feel.videoSource: Forces inline video playback and preventswebkitEnterFullscreencalls to keep video within the Mini App container.
- Message Handling: A
WeakGameScriptMessageHandlerregisters for theperformActionchannel, routing messages from JavaScript to the controller'shandleScriptMessagemethod (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, this component renders the Mini App title, verification badges usingEmojiStatusComponent, 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,WebAppSecureStorage.swift, andWebAppMessagePreviewScreen.swift. - Safe Area Handling: When layout changes occur—such as toggling fullscreen mode or keyboard appearance—
WebAppController.containerLayoutUpdatedrecalculates insets and dispatchescontent_safe_area_changedevents 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:
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
openWebAppImplinChatControllerOpenWebApp.swift, which preparesWebAppParametersand retrieves signed URLs from Telegram servers viaengine.messages.requestWebView. - WebView Implementation: The
WebAppWebViewclass inWebAppWebView.swiftconfigures per-accountWKWebsiteDataStoreisolation (iOS 17+) and injects theTelegramWebviewProxyJavaScript bridge viaeventProxySource. - Bidirectional Bridge: Native and web layers communicate via
performActionmessage handlers andevaluateJavaScript, supporting events likemain_button_pressedandviewport_changedthroughhandleScriptMessage. - UI Integration: The
WebAppControllermanages native UI elements including titles (WebAppTitleView), buttons, permissions, and safe areas while coordinating layout updates with the web content. - Lifecycle Control: Server-side
keepAliveSignalmonitoring ensures Mini Apps close automatically when bots terminate sessions, with cleanup handled inWebAppController.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, 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →