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
New templates map
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.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 samedata 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 thisdata object, with the template’s push channel mapping it through:
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 asdatainstead ofnotification, which improves delivery and lets you style the notification yourself. It also attaches an APNs override so iOS delivery tracking stays accurate. That is whytitleandbodyappear in thedatablock above. Turn it off and they arrive in a separatenotificationblock. - 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.
aps block and the google.c.* keys shown above. An Android device does not.
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.remove() on teardown, such as inside Flutter’s dispose().
Deep-link on tap
Send the destination under your own key indata, 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:
"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:
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
Why is my data missing from the push payload?
Why is my data missing from the push payload?
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.Why did my number arrive as a string on Android?
Why did my number arrive as a string on Android?
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.Can I send different data to iOS and Android?
Can I send different data to iOS and Android?
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).Is there a size limit on the data payload?
Is there a size limit on the data 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.Which provider keys does Courier use for push?
Which provider keys does Courier use for push?
firebase-fcm, apn, expo, onesignal, and pusher-beams. Use these keys to register a token and to override a provider on a send.