Notification Service Extension is an iOS extension that intercepts a push notification immediately after it is received but before it is displayed to the user. The extension can decrypt an encrypted payload, download and attach media files, and change the notification text or title in real time. According to Apple Developer Documentation (2025), to activate the extension the server must send the mutable-content:1 key in the notification attributes — this is the only condition for launching UNNotificationServiceExtension.
Key Takeaways
Notification Service Extension is an app extension in iOS that intercepts an incoming push notification on the device side and allows you to modify its content before the user sees it. This is the only type of notification extension that works with content rather than display.
The key difference from Notification Content Extension: Service Extension works before the notification is shown and can change the title, body, sound file, and attachments. Content Extension works after it is shown and only manages the visual presentation of the finished notification. These two extensions can work together: Service Extension downloads an image, and Content Extension displays it in a custom interface.
The extension activates automatically upon receiving a push notification with the mutable-content:1 attribute in the aps dictionary. iOS launches the extension in the background, passes it the original UNNotificationRequest, and waits for a modified version to display.
UNNotificationServiceExtension receives the full UNNotificationRequest with the original content. The extension can modify any fields of UNNotificationContent: title, subtitle, body, userInfo, attachments, and sound. Changes are applied before the notification is shown.
Content decryption — if a push notification contains an encrypted payload, the extension decrypts it before display. Media download — attaching an image or video to the notification. Localization — adapting the notification text to the device regional settings. Data enrichment — adding additional information from local storage or cache.
According to Apple, the most common scenario among apps is image downloading for rich media notifications. The server sends an image URL in the payload, the extension downloads it to a temporary directory and creates a UNNotificationAttachment, which the system displays in a standard or custom interface.
The extension can completely rewrite the notification text, replace the title, or add a subtitle. For example, a messenger app can receive an encrypted notification, decrypt it in the extension, and display readable text. Or a news app can add a news category to the subtitle before display.
override func didReceive(
_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
) {
let content = request.content.mutableCopy()
as! UNMutableNotificationContent
if let imageURL = content.userInfo["media-url"]
as? String {
downloadAndAttach(imageURL: imageURL,
content: content,
handler: contentHandler)
}
}
UNNotificationServiceExtension is the base class from which Service Extension inherits. The class defines two lifecycle methods: didReceive(_:withContentHandler:) — the main processing method, and serviceExtensionTimeWillExpire() — the timeout handler.
didReceive(_:withContentHandler:) is called when a notification is received. The extension receives a UNNotificationRequest and a contentHandler closure, which must be called with the modified UNMutableNotificationContent. The extension is required to call contentHandler — if it doesn't, iOS will show the original notification after the timeout expires.
Important: the extension can only process one notification at a time. If multiple notifications arrive simultaneously, iOS creates separate extension instances for each. You cannot use global state for sequential processing.
serviceExtensionTimeWillExpire() is called by the system when the remaining execution time is about to expire. In this method, you must immediately call contentHandler with whatever content is ready at that point — even if the media file has not finished downloading. If you do not call contentHandler in this method, iOS will show the original notification.
It is recommended to save minimally acceptable content in this method — for example, a notification with text and a title but without an image whose download did not complete in time.
UNNotificationAttachment is an object created by the extension to attach a media file to the notification. The extension downloads the file from the network, saves it to a temporary directory, and creates a UNNotificationAttachment specifying the content type.
UNNotificationAttachment is created using the init(identifier:url:options:) initializer. The URL must point to a local file in the temporary directory accessible to the extension. After creation, the attachment is added to the attachments array of UNMutableNotificationContent.
Apple recommends using URLSession with a background configuration for downloading — when using the standard URLSession, downloading blocks the thread and consumes time from the 30-second limit. Background URLSession continues downloading even when the extension terminates, and the result can be used on the next launch.
If the server sends an encrypted notification, the extension must decrypt the payload before calling contentHandler. Decryption typically involves requesting a key from Keychain or App Group, decrypting via CommonCrypto, and replacing the notification body or userInfo. In case of a decryption error, you should call contentHandler with the original content — so the user at least sees that a notification arrived, even if unreadable.
func downloadAndAttach(
imageURL: String,
content: UNMutableNotificationContent,
handler: @escaping (UNNotificationContent) -> Void
) {
let task = URLSession.shared.dataTask(with:
URL(string: imageURL)!) { data, _, _ in
let url = FileManager.default
.temporaryDirectory
.appendingPathComponent("image.jpg")
try? data?.write(to: url)
let attachment = try? UNNotificationAttachment(
identifier: "image", url: url)
content.attachments = [attachment].compactMap { $0 }
handler(content)
}
task.resume()
}
Notification Service Extension operates within strict time constraints. iOS allocates a fixed execution time — approximately 30 seconds from activation. If the extension has not called contentHandler within this time, the system forcibly terminates the process and shows the original notification unchanged.
It is recommended to implement a multi-level fallback: first try to download media, on success call contentHandler with full content; on failure call contentHandler with text but no media; on critical error pass the original content. This approach guarantees that the user will always see a notification rather than a blank screen.
According to Apple, the most common cause of timeouts is downloading large media files over a slow connection. To reduce the risk, it is recommended to optimize image size on the server — send previews up to 300 KB instead of full resolution. Full-size images should be downloaded when the app is opened.
To track extension timeouts and errors, you can use os_log to record diagnostic messages in the Unified Logging System. Although direct file logging in the extension is difficult, os_log allows performance analysis through Console.app on the developer device. Apple recommends adding metrics on every didReceive call — download time, file size, operation result.
override func serviceExtensionTimeWillExpire() {
let fallback = bestEffortContent as?
UNMutableNotificationContent
?? request.content.mutableCopy()
as! UNMutableNotificationContent
contentHandler(fallback)
}
Frequently Asked Questions
The server adds the mutable-content:1 key to the aps dictionary of the push notification. Without this parameter, the system ignores the extension and shows the standard notification.
No. mutable-content:1 is a mandatory condition for activating Service Extension. If the key is missing or set to 0, the notification is displayed without calling the extension.
iOS forcibly terminates the extension and shows the original notification unchanged. To avoid this, implement serviceExtensionTimeWillExpire() with minimally acceptable content.
Through App Group (shared UserDefaults or file) or Keychain with shared access between the app and the extension. Directly passing keys in the notification payload is insecure.
Up to 4 attachments per notification, each up to 50 MB. The total size of attachments affects download time — the more files, the higher the risk of timeout.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also