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

> Discover how Telegram iOS configures its App Group dynamically for seamless data sharing across the main app, widgets, notification services, and Siri intents.

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

---

**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`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/submodules/WidgetItems/Sources/WidgetItems.swift) (line 55), the derivation appears as:

```swift
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.

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

```

This dynamic construction appears consistently across [`AppDelegate.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/AppDelegate.swift), [`NotificationService.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/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.

```swift
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`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/Telegram/WidgetKitWidget/TodayViewController.swift) (lines 84-85), the widget retrieves data using the same derived path:

```swift
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`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/submodules/TelegramUI/Sources/AppDelegate.swift) (line 88), the URL session explicitly targets the App Group:

```swift
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`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/Telegram/NotificationService/Sources/NotificationService.swift) (line 757) contains:

```swift
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`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/ShareRootController.swift) (line 38) and [`IntentHandler.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/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`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/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`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/WidgetItems.swift), [`AppDelegate.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/AppDelegate.swift), [`NotificationService.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/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`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/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`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/TodayViewController.swift)), the Share extension ([`ShareRootController.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/ShareRootController.swift)), Siri Intents ([`IntentHandler.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/IntentHandler.swift)), the Notification Service extension ([`NotificationService.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/NotificationService.swift)), the Notification Content extension ([`NotificationViewController.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/NotificationViewController.swift)), and the Broadcast Upload extension ([`BroadcastUploadExtension.swift`](https://github.com/TelegramMessenger/Telegram-iOS/blob/main/BroadcastUploadExtension.swift)).