Skip to main content
Notification service extensions let you intercept and modify push notifications before they are displayed to the user. Use them to add rich media, customize appearance, handle background data, and enable confirmed delivery analytics.
For a full reference of the notification payload structure, see OSNotification payload.

Android notification service extension

The Android notification service extension allows you to process notifications before they are shown to the user. Common use cases include:
  • Receiving data in the background with or without displaying a notification
  • Overriding notification settings based on client-side app logic, such as custom accent color, vibration pattern, or other NotificationCompat options

Step 1: Create a class for the service extension

Create a class that implements INotificationServiceExtension and its onNotificationReceived method. The onNotificationReceived method receives an event parameter of type INotificationReceivedEvent.
package your.package.name

import androidx.annotation.Keep;
import com.onesignal.notifications.IActionButton;
import com.onesignal.notifications.IDisplayableMutableNotification;
import com.onesignal.notifications.INotificationReceivedEvent;
import com.onesignal.notifications.INotificationServiceExtension;

@Keep
public class NotificationServiceExtension implements INotificationServiceExtension {

     @Override
     public void onNotificationReceived(INotificationReceivedEvent event) {
        IDisplayableMutableNotification notification = event.getNotification();

        if (notification.getActionButtons() != null) {
           for (IActionButton button : notification.getActionButtons()) {
              // Modify action buttons here
           }
        }
     }
}
The @Keep annotation is required in both Java and Kotlin to prevent code shrinking tools (R8 or ProGuard) from renaming or removing your class when minification is enabled.

Step 2: Customize the notification

Add your customization logic inside the onNotificationReceived method before registering the extension. The following tabs show common patterns.
Use event.preventDefault() to suppress automatic display, then call display() after performing async work.
event.preventDefault();

new Thread(() -> {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException ignored) {}

    event.getNotification().display();
}).start();
If you call event.preventDefault() but never call display(), the notification is silently dropped. See Duplicated notifications for details on avoiding duplicate displays.

Step 3: Add the service extension to your AndroidManifest.xml

Add the class name and value as meta-data within the <application> tag of your AndroidManifest.xml file. Ignore any “unused” warnings.
XML
<application>
  <meta-data
    android:name="com.onesignal.NotificationServiceExtension"
    android:value="com.onesignal.example.NotificationServiceExtension" />
</application>
Replace com.onesignal.example.NotificationServiceExtension with your class’s fully qualified name.

iOS notification service extension

The UNNotificationServiceExtension allows you to modify push notification content before it is displayed. It is required for: If you followed the Mobile SDK setup, the notification service extension is already configured. This section explains how to access the OneSignal notification payload data and troubleshoot issues.

Getting the iOS push payload

Inside your didReceive(_:withContentHandler:) override, you call OneSignalExtension.didReceiveNotificationExtensionRequest to let OneSignal process the notification and invoke the content handler. Read or modify bestAttemptContent before calling this method. In this example, a notification is sent with the following data:
JSON
{
  "app_id": "YOUR_APP_ID",
  "target_channel": "push",
  "headings": {"en": "The message title"},
  "contents": {"en": "The message contents"},
  "data":{
    "additional_data_key_1":"value_1",
    "additional_data_key_2":"value_2"
    },
  "include_subscription_ids": ["SUBSCRIPTION_ID_1"]
}
Access this additional data within the OneSignalNotificationServiceExtension via the a key inside the custom dictionary of userInfo:
if let bestAttemptContent = bestAttemptContent {

    if let customData = bestAttemptContent.userInfo["custom"] as? [String: Any],
       let additionalData = customData["a"] as? [String: Any] {

        if let jsonData = try? JSONSerialization.data(withJSONObject: additionalData, options: .prettyPrinted),
           let jsonString = String(data: jsonData, encoding: .utf8) {
            print("The additionalData dictionary in JSON format:\n\(jsonString)")
        } else {
            print("Failed to convert additionalData to JSON format.")
        }
    }

    if let messageData = bestAttemptContent.userInfo["aps"] as? [String: Any],
       let apsData = messageData["alert"] as? [String: Any],
       let body = apsData["body"] as? String,
       let title = apsData["title"] as? String {
        print("The message contents is: \(body), message headings is: \(title)")
    } else {
        print("Unable to retrieve apsData")
    }

    OneSignalExtension.didReceiveNotificationExtensionRequest(self.receivedRequest,
                                                              with: bestAttemptContent,
                                                              withContentHandler: self.contentHandler)
}
Example console output:
The additionalData dictionary in JSON format:
{
  "additional_data_key_1" : "value_1",
  "additional_data_key_2" : "value_2"
}
The message contents is: The message contents, message headings is: The message title

Troubleshooting the iOS notification service extension

Use this section to debug issues with images, action buttons, or confirmed deliveries not showing on iOS.

Check your Xcode settings

In General > Targets, verify that your main app target and OneSignalNotificationServiceExtension target have the same:
  • Supported Destinations
  • Minimum Deployment (iOS 14.5 or higher)
If you are using CocoaPods, make sure these match with your main target in the Podfile to avoid build errors.
Xcode General tab showing Supported Destinations and Minimum Deployment for the main app target
Xcode General tab showing Supported Destinations and Minimum Deployment for the notification service extension target
In the OneSignalNotificationServiceExtension > Info tab, expand the NSExtension key and verify it contains:
XML
<dict>
  <key>NSExtensionPointIdentifier</key>
  <string>com.apple.usernotifications.service</string>
  <key>NSExtensionPrincipalClass</key>
  <string>$(PRODUCT_MODULE_NAME).NotificationService</string>
</dict>
Xcode Info tab showing the NSExtension dictionary with NSExtensionPointIdentifier and NSExtensionPrincipalClass keys
If using Objective-C, replace $(PRODUCT_MODULE_NAME).NotificationService with NotificationService.

Turn off “Copy only when installing”

Select your main app target > Build Phases > Embed App Extensions. Ensure “Copy only when installing” is not checked:
Xcode Build Phases tab showing Embed App Extensions with Copy only when installing unchecked

Debugging the iOS notification service extension

Follow these steps to verify the notification service extension is set up correctly.

1. Update the OneSignalNotificationServiceExtension code

Open NotificationService.m or NotificationService.swift and replace the file contents with the code below. This adds logging to help verify the extension is running. Replace YOUR_BUNDLE_ID with your actual Bundle ID.
import UserNotifications
import OneSignalExtension
import os.log

class NotificationService: UNNotificationServiceExtension {

    var contentHandler: ((UNNotificationContent) -> Void)?
    var receivedRequest: UNNotificationRequest!
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.receivedRequest = request
        self.contentHandler = contentHandler
        self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

        let userInfo = request.content.userInfo
        let custom = userInfo["custom"]
        print("Running NotificationServiceExtension: userInfo = \(userInfo.description)")
        print("Running NotificationServiceExtension: custom = \(custom.debugDescription)")
        os_log("%{public}@", log: OSLog(subsystem: "YOUR_BUNDLE_ID", category: "OneSignalNotificationServiceExtension"), type: OSLogType.debug, userInfo.debugDescription)

        if let bestAttemptContent = bestAttemptContent {
            // Prepend "[Modified]" to confirm the extension is running
            print("Running NotificationServiceExtension")
            bestAttemptContent.body = "[Modified] " + bestAttemptContent.body

            OneSignalExtension.didReceiveNotificationExtensionRequest(self.receivedRequest, with: bestAttemptContent, withContentHandler: self.contentHandler)
        }
    }

    override func serviceExtensionTimeWillExpire() {
        if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
            OneSignalExtension.serviceExtensionTimeWillExpireRequest(self.receivedRequest, with: self.bestAttemptContent)
            contentHandler(bestAttemptContent)
        }
    }
}
Debug log types need to be enabled in Console via Action > Include Debug Messages.

2. Change your active scheme

Set your Active Scheme to OneSignalNotificationServiceExtension.
Xcode toolbar showing the active scheme dropdown set to OneSignalNotificationServiceExtension

3. Build and run the project

Build and run the project in Xcode on a real device.

4. Open the console

In Xcode, select Window > Devices and Simulators.
Xcode menu showing the Window dropdown with Devices and Simulators selected
You should see your device connected. Select Open Console.
Xcode Devices window showing the Open Console button for a connected device

5. Check the console

In the Console:
  1. Select Action > Include Debug Messages
  2. Search for OneSignalNotificationServiceExtension as the CATEGORY
  3. Select Start
macOS Console app showing the category filter and Start button for debugging the notification service extension
Send a notification to the device with a message body (use the contents property if sending from the Create notification API). Example payload:
cURL
curl --request POST \
 --url 'https://api.onesignal.com/notifications' \
 --header 'Authorization: Key YOUR_API_KEY' \
 --header 'accept: application/json' \
 --header 'content-type: application/json' \
 --data '
{
"app_id": "YOUR_APP_ID",
"target_channel": "push",
"headings": {"en": "The message title"},
"contents": {"en": "The message contents"},
"data":{"additional_data_key_1":"value_1","additional_data_key_2":"value_2"},
"include_subscription_ids": [
"SUBSCRIPTION_ID_1"
]
}'
You should see a message logged whether the app is running or not.
macOS Console app showing debug log output from the OneSignalNotificationServiceExtension
If you do not see a message, remove OneSignal from your app and follow the Mobile SDK setup again to verify the integration.

FAQ

Why isn’t my notification service extension running on iOS?

The extension only runs when mutable-content is set in the push payload. OneSignal sets this automatically when you include an attachment or action buttons. Verify your Xcode settings match the requirements in the troubleshooting section.

Can I prevent a notification from displaying on Android?

Yes. Call event.preventDefault() in your onNotificationReceived method to suppress automatic display. You can then call event.getNotification().display() to show it later, or never call display() to silently drop the notification without displaying it. See Duplicated notifications for details on avoiding duplicates when using this pattern.

Do I need the @Keep annotation on Android?

Yes. The @Keep annotation prevents ProGuard or R8 from renaming or removing your class that implements INotificationServiceExtension during minification. Without it, OneSignal cannot find the class at runtime.

Mobile SDK setup

Install and configure the OneSignal SDK for iOS and Android.

Images & rich media

Attach images, GIFs, and video to push notifications.

Confirmed delivery

Track confirmed notification delivery to devices.

Duplicated notifications

Troubleshoot duplicate push notifications on all platforms.