Skip to main content
A push carries more than a title and body. The data object rides alongside the alert, and your app reads it on arrival or tap. Put your fields in a data object on the message. One data object covers FCM, APNs, and Expo, so you describe the payload once. Send your push template with a data object using :
Older templates need a route for data.
New templates map data into the FCM and APNs payload by default, and a send with inline content needs no mapping at all. Having trouble getting your fields through on an older template? covers it. Expo forwards data either way.
Courier adds one key of its own to the same object, trackingUrl, for delivery and click tracking. Treat that name as reserved.

How the data arrives per provider

Each provider has its own payload rules, so the same data object lands in a different shape. FCM flattens everything to strings. FCM only accepts string values in data, so Courier converts anything else with JSON.stringify. Send { "nested": { "carrier": "UPS" } } and the device receives nested as the string {"carrier":"UPS"}, which your app has to parse. Numbers arrive as strings too: badgeCount: 3 arrives as "3". Expo forwards data on every send. The Expo handler copies the request’s data straight into the payload, so it arrives with no data mapping.

The same send, side by side

Sending this data object, with the template’s push channel mapping it through:
produces two different payloads on the device:
On APNs your keys are siblings of aps and keep their JSON types. On FCM they live in the data block and are all strings. Both payloads reflect Courier’s default provider settings, tuned for the mobile SDKs:
  • FCM: Apply Recommended Courier Mobile SDK Formatting (remapData), on by default. It ships the message as data instead of notification, which improves delivery and lets you style the notification yourself. It also attaches an APNs override so iOS delivery tracking stays accurate. That is why title and body appear in the data block above. Turn it off and they arrive in a separate notification block.
  • APNs: Attach Mutable Content (mutableContent), on by default. It adds "mutable-content": 1, which lets a see the message before it reaches the user and record the delivery. Turn it off and messages still arrive, but delivery metrics get less accurate.
An iOS device receiving through FCM also gets the aps block and the google.c.* keys shown above. An Android device does not.
Flat, already-stringified data values avoid parsing on Android. One level of string keys and string values behaves identically on FCM, APNs, and Expo.

Read the data on the device

The Courier mobile SDKs hand you the full payload on delivery and on tap. Read your keys from it and route.
On iOS and Android the handler is a method on a class you already own, so it lives as long as your app does. Flutter and React Native return a listener instead, and it holds a reference until you drop it. Call remove() on teardown, such as inside Flutter’s dispose(). Send the destination under your own key in data, then read it in your tap handler and route. The example at the top of this page uses screen. Any URL works there: a custom scheme like acme://orders/ord_4821, or a universal link like https://acme.com/orders/ord_4821. Universal links are set up on the platform, not in Courier. Courier carries the string, and the operating system decides whether it opens your app: The tap handler is the one in Read the data on the device above.

Send data with no visible alert

A silent push shows the user nothing. Use it to move the app icon badge, or to wake your app in the background and sync. providers.apn.override.silent keeps the alert off the payload, so nothing appears on the lock screen. Pair it with badge and APNs moves the number itself, with no code running in your app:
To wake your app in the background as well, add "content-available": 1 to the same body. iOS then launches your app to handle the payload, which is what you want for syncing content before the user opens it. A badge on its own does not need it. On FCM, the message already ships as data with no notification block, because Apply Recommended Courier Mobile SDK Formatting (remapData) is on by default. Courier moves title, body, and image into the data block alongside your own keys. Your app reads all of it as data and decides what to display. If that setting is off on your integration, switch it back on for a single send:
Your app needs background delivery configured either way: Background Modes → Remote notifications on iOS, and a FirebaseMessagingService that handles onMessageReceived on Android.

Verify the payload

1

Send to a real device

Send the request above to a user_id with a registered token. Simulators do not receive push.
2

Confirm the payload on the device

Log the full message in your delivery or tap handler and check your keys are present. On Android, every value is a string.
3

Check the log if a key is missing

Open and open the message. The provider request shows the exact payload Courier sent, including the data block. Fields missing there were dropped by the send, not the device. The usual cause is a template send with no data mapping.

FAQ

Check the template’s age. New templates map data through by default, but one created before that default uses data for variable substitution only. shows how to turn it on, or set the fields in a . Expo forwards data either way.
FCM only permits string values in its data block, so Courier stringifies everything else. 3 becomes "3" and { "a": "b" } becomes "{\"a\":\"b\"}". Parse on the device, or send flat strings to begin with.
Send the shared fields in the top-level data object, then add per-platform fields in each provider’s override (firebase-fcm under override.body.data, apn under override.body.payload).
The provider sets it, not Courier. APNs allows 4KB for a normal push, and FCM allows 4KB for the data block. The provider rejects anything over the limit and the message goes UNDELIVERABLE. Send IDs and let the app fetch the rest.
firebase-fcm, apn, expo, onesignal, and pusher-beams. Use these keys to register a token and to override a provider on a send.