Notification extensions enhance push notifications with rich content, media attachments, and custom actions.
Extension types
Two notification extension types are available:
Notification Content
Custom UI for displaying rich notification content
Notification Service
Modify notification payloads before delivery
Notification Content Extension
Displays custom UI when the user expands a notification.
Extension point
| Property | Value |
|---|
| Type | notification-content |
| Extension Point | com.apple.usernotifications.content-extension |
| Frameworks | UserNotifications, UserNotificationsUI |
Creating a content extension
npx create-target notification-content
Implementation
import UserNotifications
import UserNotificationsUI
class NotificationViewController: UIViewController, UNNotificationContentExtension {
@IBOutlet var label: UILabel?
func didReceive(_ notification: UNNotification) {
// Extract data from notification
let content = notification.request.content
label?.text = content.body
// Access custom payload
if let customData = content.userInfo["customData"] as? String {
// Use custom data
}
}
// Handle notification actions
func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void) {
if response.actionIdentifier == "LIKE_ACTION" {
// Handle like action
completion(.dismissAndForwardAction)
} else {
completion(.dismiss)
}
}
}
In Info.plist, specify which notification categories use this extension:
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>UNNotificationExtensionCategory</key>
<string>myNotificationCategory</string>
<key>UNNotificationExtensionInitialContentSizeRatio</key>
<real>1.0</real>
</dict>
</dict>
Send a notification with this category:
{
"aps": {
"alert": "New message",
"category": "myNotificationCategory"
},
"customData": "value"
}
Notification Service Extension
Modifies notification payloads before they’re delivered to the user. Common uses:
- Download and attach images/videos
- Decrypt encrypted messages
- Update notification content
- Track delivery
Extension point
| Property | Value |
|---|
| Type | notification-service |
| Extension Point | com.apple.usernotifications.service |
| Frameworks | UserNotifications |
Creating a service extension
npx create-target notification-service
Implementation
import UserNotifications
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
// Download and attach an image
if let imageURLString = request.content.userInfo["image_url"] as? String,
let imageURL = URL(string: imageURLString),
let imageData = try? Data(contentsOf: imageURL),
let attachment = self.createAttachment(data: imageData, identifier: "image") {
bestAttemptContent.attachments = [attachment]
}
// Decrypt content if needed
if let encryptedBody = request.content.userInfo["encrypted_body"] as? String {
bestAttemptContent.body = decrypt(encryptedBody)
}
contentHandler(bestAttemptContent)
}
}
override func serviceExtensionTimeWillExpire() {
// Called when extension is about to be terminated
// Deliver the best attempt content
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
private func createAttachment(data: Data, identifier: String) -> UNNotificationAttachment? {
let fileManager = FileManager.default
let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString
let tmpSubFolderURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(tmpSubFolderName, isDirectory: true)
do {
try fileManager.createDirectory(at: tmpSubFolderURL, withIntermediateDirectories: true, attributes: nil)
let fileURL = tmpSubFolderURL.appendingPathComponent(identifier)
try data.write(to: fileURL)
let attachment = try UNNotificationAttachment(identifier: identifier, url: fileURL, options: nil)
return attachment
} catch {
print("Error creating attachment: \(error)")
}
return nil
}
private func decrypt(_ encrypted: String) -> String {
// Implement your decryption logic
return encrypted
}
}
Push notification payload:
{
"aps": {
"alert": "Check out this photo!",
"mutable-content": 1
},
"image_url": "https://example.com/photo.jpg"
}
The mutable-content: 1 flag is required for the service extension to run.
Interactive notifications
Define custom notification actions in your main app:
import UserNotifications
func registerNotificationActions() {
let likeAction = UNNotificationAction(
identifier: "LIKE_ACTION",
title: "Like",
options: [.foreground]
)
let commentAction = UNNotificationAction(
identifier: "COMMENT_ACTION",
title: "Comment",
options: [.foreground]
)
let category = UNNotificationCategory(
identifier: "myNotificationCategory",
actions: [likeAction, commentAction],
intentIdentifiers: [],
options: [.customDismissAction]
)
UNUserNotificationCenter.current().setNotificationCategories([category])
}
Supported attachment types:
| Type | Extensions |
|---|
| Images | .jpg, .jpeg, .png, .gif |
| Audio | .mp3, .wav, .aiff |
| Video | .mp4, .m4v, .mov |
Size limits:
- Images: 10 MB
- Audio: 5 MB
- Video: 50 MB
Best practices
- Use URLSession for downloads (supports background)
- Set appropriate timeouts
- Handle network failures gracefully
- Cache downloaded media when possible
- Always implement
serviceExtensionTimeWillExpire()
- Deliver best-attempt content even on failure
- Log errors for debugging
- Test with poor network conditions
- Validate image URLs
- Implement encryption for sensitive data
- Don’t store sensitive data in notifications
- Use App Groups for secure data sharing
Troubleshooting
- Verify
mutable-content: 1 in notification payload
- Check notification category matches Info.plist
- Ensure extension is properly code signed
- Test on a real device (not simulator for remote notifications)
- Verify image URL is accessible
- Check image file size is under 10 MB
- Ensure attachment identifier is unique
- Verify file extension is supported
Learn more
Notifications guide
Detailed notification guide
Target config
Configure notification extensions
Complete target list
All extension types
Apple documentation
Official UserNotifications documentation