How App Group is Configured in Telegram-iOS for Cross-Extension Data Sharing

Telegram-iOS derives a single App Group identifier dynamically from the base bundle identifier and reuses it across all extensions to enable seamless data sharing between the main app, widgets, notification services, and Siri intents.

The TelegramMessenger/Telegram-iOS repository implements a centralized pattern for App Group configuration that avoids hard-coded identifiers. By deriving the group name from the main bundle's base identifier, the codebase ensures that every extension—from WidgetKit widgets to background URL sessions—accesses the same shared container without manual synchronization of entitlements files.

Deriving the Base Bundle Identifier

The configuration starts by extracting the root organization identifier from the main bundle. The code locates the last dot in the bundle identifier and truncates everything after it, converting org.telegram.Telegram into org.telegram.

In submodules/WidgetItems/Sources/WidgetItems.swift (line 55), the derivation appears as:

let appBundleIdentifier = Bundle.main.bundleIdentifier!
guard let lastDot = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
    return WidgetPresentationData.default
}
let baseAppBundleId = String(appBundleIdentifier[..<lastDot.lowerBound])

This baseAppBundleId becomes the foundation for all shared container references throughout the app and its extensions.

Constructing the App Group Identifier

Following Apple's convention, Telegram-iOS prepends group. to the base identifier to form the App Group name. This string is never hard-coded, allowing the same codebase to support multiple app variants (such as TestFlight or Enterprise builds) without modification.

let appGroupName = "group.\(baseAppBundleId)"  // Results in "group.org.telegram"

This dynamic construction appears consistently across AppDelegate.swift, NotificationService.swift, and extension entry points.

Accessing the Shared Container URL

Once the group name is constructed, the system resolves the shared container path using FileManager. This URL serves as the root directory for all cross-process data exchange.

let maybeAppGroupUrl = FileManager.default.containerURL(
    forSecurityApplicationGroupIdentifier: appGroupName)
guard let appGroupUrl = maybeAppGroupUrl else {
    // Handle missing entitlements
    return
}
let rootPath = appGroupUrl.path

The rootPath variable is then passed to storage controllers, encryption modules, and background session configurations.

Implementation Across Extensions

Widget Data Storage

Widget extensions access the shared container to read WidgetPresentationData files. In Telegram/WidgetKitWidget/TodayViewController.swift (lines 84-85), the widget retrieves data using the same derived path:

let appGroupName = "group.\(baseAppBundleId)"
let containerUrl = FileManager.default.containerURL(
    forSecurityApplicationGroupIdentifier: appGroupName)

Background URL Sessions

The main app configures background transfers visible to extensions by setting the sharedContainerIdentifier property. In submodules/TelegramUI/Sources/AppDelegate.swift (line 88), the URL session explicitly targets the App Group:

let configuration = URLSessionConfiguration.background(withIdentifier: identifier)
configuration.sharedContainerIdentifier = appGroupName
configuration.isDiscretionary = false
let session = URLSession(configuration: configuration,
                         delegate: self,
                         delegateQueue: .main)

Notification Service Extensions

The push notification extension verifies the shared container before processing incoming payloads. Telegram/NotificationService/Sources/NotificationService.swift (line 757) contains:

let appBundleIdentifier = Bundle.main.bundleIdentifier!
let baseAppBundleId = String(appBundleIdentifier[..<appBundleIdentifier.range(of: ".", options: [.backwards])!.lowerBound])
let appGroupName = "group.\(baseAppBundleId)"
let maybeAppGroupUrl = FileManager.default.containerURL(
    forSecurityApplicationGroupIdentifier: appGroupName)

Share Extensions and Siri Intents

Both ShareRootController.swift (line 38) and IntentHandler.swift (line 101) instantiate their data layers using the identical derivation logic, ensuring that shared media and intent responses write to the same filesystem space accessible by the main app.

Broadcast Upload Extensions

Screen recording extensions also require container access. Telegram/BroadcastUpload/BroadcastUploadExtension.swift (line 324) configures its file manager with the same group. prefix pattern.

Encryption and Security Integration

Beyond file sharing, the App Group container stores sensitive encryption parameters. The BuildConfig.deviceSpecificEncryptionParameters(rootPath:, baseAppBundleId:) method receives appGroupUrl.path to establish per-device encryption keys that remain accessible even when the main app is not foregrounded.

Summary

  • Derive dynamically: Telegram-iOS generates the App Group identifier by stripping the last component from Bundle.main.bundleIdentifier and prepending group..
  • Single source of truth: The same construction logic appears in WidgetItems.swift, AppDelegate.swift, NotificationService.swift, and six other extension entry points.
  • Background session support: The sharedContainerIdentifier property of URLSessionConfiguration binds downloads to the App Group, allowing extensions to access transferred files.
  • Encryption ready: The shared container path feeds directly into BuildConfig.deviceSpecificEncryptionParameters for secure key storage.

Frequently Asked Questions

What naming convention does Telegram-iOS use for App Groups?

Telegram-iOS uses the format group.<base-bundle-id>, where the base identifier is derived by removing the last dot-separated component from the main bundle identifier. For example, org.telegram.Telegram becomes group.org.telegram.

How do notification extensions access shared data in Telegram-iOS?

The notification service extension in NotificationService.swift derives the same baseAppBundleId as the main app, constructs the group. prefixed identifier, and calls FileManager.default.containerURL(forSecurityApplicationGroupIdentifier:) to obtain the shared container path for reading cached data or encryption keys.

Why does Telegram-iOS derive the App Group identifier at runtime instead of hardcoding it?

Deriving the identifier at runtime allows the codebase to support multiple build targets (App Store, TestFlight, Enterprise) without modifying source files. Since the bundle identifier changes between these targets, calculating the group name from the base bundle ensures the correct entitlements are always referenced.

Which components share the App Group container besides the main app?

According to the source analysis, the following components share the container: WidgetKit widgets (TodayViewController.swift), the Share extension (ShareRootController.swift), Siri Intents (IntentHandler.swift), the Notification Service extension (NotificationService.swift), the Notification Content extension (NotificationViewController.swift), and the Broadcast Upload extension (BroadcastUploadExtension.swift).

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →