{
"data": {
"sender": "stream.feeds",
"type": "{{ event_type }}",
"body": "{{ truncate comment.text 150 }}",
"title": "{{#if isMentionedInComment}}{{ sender.name }} mentioned you in a comment{{else}}{{#if isActivityOwner}}{{ sender.name }} commented on your activity{{else}}{{ sender.name }} commented on an activity you were mentioned in{{/if}}{{/if}}",
"fid": "{{ feed.fid }}",
"activity_id": "{{ activity.id }}",
"comment_id": "{{ comment.id }}",
"receiver_id": "{{ receiver.id }}"
},
"android": {
"priority": "high"
},
"apns": {
"payload": {
"aps": {
"alert": {
"title": "{{#if isMentionedInComment}}{{ sender.name }} mentioned you in a comment{{else}}{{#if isActivityOwner}}{{ sender.name }} commented on your activity{{else}}{{ sender.name }} commented on an activity you were mentioned in{{/if}}{{/if}}",
"body": "{{ truncate comment.text 150 }}"
},
"sound": "default",
"mutable-content": 1,
"content-available": 0,
"category": "stream.feeds"
}
}
}
}
Customizing Notifications
Customizing push content is optional, as Activity Feeds provides well-designed default templates. Just make sure to enable push notifications for the event types you plan to support.
Once push notifications are enabled for your app, you can customize the templates to match your app’s needs. This lets you control how notifications appear on users’ devices for activities, reactions, comments, and other feed events.
Push Notification Templates
Activity Feeds supports templating for both Firebase and APNs. Configuring templates is optional — if no custom templates are provided, Activity Feeds will automatically use default templates for Firebase and APNs.
Default Templates
Activity Feeds uses specific default templates for different notification types. Here are the actual templates used by the system:
Comment Added - Firebase Template
Comment Added - APN Template
{
"payload": {
"aps": {
"alert": {
"title": "{{#if isMentionedInComment}}{{ sender.name }} mentioned you in a comment{{else}}{{#if isActivityOwner}}{{ sender.name }} commented on your activity{{else}}{{ sender.name }} commented on an activity you were mentioned in{{/if}}{{/if}}",
"body": "{{ truncate comment.text 150 }}"
},
"sound": "default",
"mutable-content": 1,
"content-available": 0,
"category": "stream.feeds"
},
"stream": {
"sender": "stream.feeds",
"type": "{{ event_type }}",
"fid": "{{ feed.fid }}",
"activity_id": "{{ activity.id }}",
"comment_id": "{{ comment.id }}",
"receiver_id": "{{ receiver.id }}"
}
}
}
Activity Added - Firebase Template
{
"data": {
"sender": "stream.feeds",
"type": "{{ event_type }}",
"body": "{{ truncate activity.text 150 }}",
"title": "{{#if isMentioned}}{{ sender.name }} mentioned you in an activity{{else}}{{ sender.name }} posted a new activity{{/if}}",
"fid": "{{ feed.fid }}",
"activity_id": "{{ activity.id }}",
"receiver_id": "{{ receiver.id }}"
},
"android": {
"priority": "high"
},
"apns": {
"payload": {
"aps": {
"alert": {
"title": "{{#if isMentioned}}{{ sender.name }} mentioned you in an activity{{else}}{{ sender.name }} posted a new activity{{/if}}",
"body": "{{ truncate activity.text 150 }}"
},
"sound": "default",
"mutable-content": 1,
"content-available": 0,
"category": "stream.feeds"
}
}
}
}
Activity Added - APN Template
{
"payload": {
"aps": {
"alert": {
"title": "{{#if isMentioned}}{{ sender.name }} mentioned you in an activity{{else}}{{ sender.name }} posted a new activity{{/if}}",
"body": "{{ truncate activity.text 150 }}"
},
"sound": "default",
"mutable-content": 1,
"content-available": 0,
"category": "stream.feeds"
},
"stream": {
"sender": "stream.feeds",
"type": "{{ event_type }}",
"fid": "{{ feed.fid }}",
"activity_id": "{{ activity.id }}",
"receiver_id": "{{ receiver.id }}"
}
}
}
Activity Reaction Added - Firebase Template
{
"data": {
"sender": "stream.feeds",
"type": "{{ event_type }}",
"body": "Reacted :{{ reaction.type }}: to your activity",
"title": "{{ sender.name }}",
"fid": "{{ feed.fid }}",
"activity_id": "{{ activity.id }}",
"reaction_type": "{{ reaction.type }}",
"receiver_id": "{{ receiver.id }}"
},
"android": {
"priority": "high"
},
"apns": {
"payload": {
"aps": {
"alert": {
"title": "{{ sender.name }}",
"body": "Reacted :{{ reaction.type }}: to your activity"
},
"sound": "default",
"mutable-content": 1,
"content-available": 0,
"category": "stream.feeds"
}
}
}
}
Activity Reaction Added - APN Template
{
"payload": {
"aps": {
"alert": {
"title": "{{ sender.name }}",
"body": "Reacted :{{ reaction.type }}: to your activity"
},
"sound": "default",
"mutable-content": 1,
"content-available": 0,
"category": "stream.feeds"
},
"stream": {
"sender": "stream.feeds",
"type": "{{ event_type }}",
"fid": "{{ feed.fid }}",
"activity_id": "{{ activity.id }}",
"reaction_type": "{{ reaction.type }}",
"receiver_id": "{{ receiver.id }}"
}
}
}
Comment Reaction Added - Firebase Template
{
"data": {
"sender": "stream.feeds",
"type": "{{ event_type }}",
"body": "Reacted :{{ reaction.type }}: to your comment",
"title": "{{ sender.name }}",
"fid": "{{ feed.fid }}",
"activity_id": "{{ activity.id }}",
"comment_id": "{{ comment.id }}",
"reaction_type": "{{ reaction.type }}",
"receiver_id": "{{ receiver.id }}"
},
"android": {
"priority": "high"
},
"apns": {
"payload": {
"aps": {
"alert": {
"title": "{{ sender.name }}",
"body": "Reacted :{{ reaction.type }}: to your comment"
},
"sound": "default",
"mutable-content": 1,
"content-available": 0,
"category": "stream.feeds"
}
}
}
}
Comment Reaction Added - APN Template
{
"payload": {
"aps": {
"alert": {
"title": "{{ sender.name }}",
"body": "Reacted :{{ reaction.type }}: to your comment"
},
"sound": "default",
"mutable-content": 1,
"content-available": 0,
"category": "stream.feeds"
},
"stream": {
"sender": "stream.feeds",
"type": "{{ event_type }}",
"fid": "{{ feed.fid }}",
"activity_id": "{{ activity.id }}",
"comment_id": "{{ comment.id }}",
"reaction_type": "{{ reaction.type }}",
"receiver_id": "{{ receiver.id }}"
}
}
}
Follow Created - Firebase Template
{
"data": {
"sender": "stream.feeds",
"type": "{{ event_type }}",
"title": "{{ sender.name }} started following you",
"fid": "{{ feed.fid }}",
"source_fid": "{{ follow.source_feed.FID }}",
"target_fid": "{{ follow.target_feed.FID }}",
"receiver_id": "{{ receiver.id }}"
},
"android": {
"priority": "high"
},
"apns": {
"payload": {
"aps": {
"alert": {
"title": "{{ sender.name }} started following you"
},
"sound": "default",
"mutable-content": 1,
"content-available": 0,
"category": "stream.feeds"
}
}
}
}
Follow Created - APN Template
{
"payload": {
"aps": {
"alert": {
"title": "{{ sender.name }} started following you"
},
"sound": "default",
"mutable-content": 1,
"content-available": 0,
"category": "stream.feeds"
},
"stream": {
"sender": "stream.feeds",
"type": "{{ event_type }}",
"fid": "{{ feed.fid }}",
"source_fid": "{{ follow.source_feed.FID }}",
"target_fid": "{{ follow.target_feed.FID }}",
"receiver_id": "{{ receiver.id }}"
}
}
}
Activity Reaction - Firebase Template
{
"data": {
"sender": "stream.feeds",
"type": "{{ event_type }}",
"body": "Reacted :{{ reaction.type }}: to your activity",
"title": "{{ sender.name }}",
"fid": "{{ feed.fid }}",
"activity_id": "{{ activity.id }}",
"reaction_type": "{{ reaction.type }}",
"receiver_id": "{{ receiver.id }}"
},
"android": {
"priority": "high"
},
"apns": {
"payload": {
"aps": {
"alert": {
"title": "{{ sender.name }}",
"body": "Reacted :{{ reaction.type }}: to your activity"
},
"sound": "default",
"mutable-content": 1,
"content-available": 0,
"category": "stream.feeds"
},
"stream": {
"sender": "stream.feeds",
"type": "{{ event_type }}",
"fid": "{{ feed.fid }}",
"activity_id": "{{ activity.id }}",
"reaction_type": "{{ reaction.type }}",
"receiver_id": "{{ receiver.id }}"
}
}
}
}
Activity Reaction - APN Template
{
"payload": {
"aps": {
"alert": {
"title": "{{ sender.name }}",
"body": "Reacted :{{ reaction.type }}: to your activity"
},
"sound": "default",
"mutable-content": 1,
"content-available": 0,
"category": "stream.feeds"
},
"stream": {
"sender": "stream.feeds",
"type": "{{ event_type }}",
"fid": "{{ feed.fid }}",
"activity_id": "{{ activity.id }}",
"reaction_type": "{{ reaction.type }}",
"receiver_id": "{{ receiver.id }}"
}
}
}
Comment Reaction - Firebase Template
{
"data": {
"sender": "stream.feeds",
"type": "{{ event_type }}",
"body": "Reacted :{{ reaction.type }}: to your comment",
"title": "{{ sender.name }}",
"fid": "{{ feed.fid }}",
"activity_id": "{{ activity.id }}",
"comment_id": "{{ comment.id }}",
"reaction_type": "{{ reaction.type }}",
"receiver_id": "{{ receiver.id }}"
},
"android": {
"priority": "high"
},
"apns": {
"payload": {
"aps": {
"alert": {
"title": "{{ sender.name }}",
"body": "Reacted :{{ reaction.type }}: to your comment"
},
"sound": "default",
"mutable-content": 1,
"content-available": 0,
"category": "stream.feeds"
},
"stream": {
"sender": "stream.feeds",
"type": "{{ event_type }}",
"fid": "{{ feed.fid }}",
"activity_id": "{{ activity.id }}",
"comment_id": "{{ comment.id }}",
"reaction_type": "{{ reaction.type }}",
"receiver_id": "{{ receiver.id }}"
}
}
}
}
Comment Reaction - APN Template
{
"payload": {
"aps": {
"alert": {
"title": "{{ sender.name }}",
"body": "Reacted :{{ reaction.type }}: to your comment"
},
"sound": "default",
"mutable-content": 1,
"content-available": 0,
"category": "stream.feeds"
},
"stream": {
"sender": "stream.feeds",
"type": "{{ event_type }}",
"fid": "{{ feed.fid }}",
"activity_id": "{{ activity.id }}",
"comment_id": "{{ comment.id }}",
"reaction_type": "{{ reaction.type }}",
"receiver_id": "{{ receiver.id }}"
}
}
}
Notification Feed Updated - Firebase Template
{
"data": {
"sender": "stream.feeds",
"type": "{{ event_type }}",
"title": "{{ notification_title }}",
"body": "{{ notification_body }}",
"fid": "{{ feed.fid }}",
"receiver_id": "{{ receiver.id }}"
},
"android": {
"priority": "high",
"collapse_key": "notification_feed_{{ aggregated_activity.group }}"
},
"apns": {
"headers": {
"apns-collapse-id": "notification_feed_{{ aggregated_activity.group }}"
},
"payload": {
"aps": {
"alert": {
"title": "{{ notification_title }}",
"body": "{{ notification_body }}"
},
"badge": {{ notification_status.unseen_count }},
"sound": "default",
"mutable-content": 1,
"content-available": 0,
"category": "stream.feeds"
}
}
}
}
Notification Feed Updated - APN Template
{
"collapse_id": "notification_feed_{{ aggregated_activity.group }}",
"payload": {
"aps": {
"alert": {
"title": "{{ notification_title }}",
"body": "{{ notification_body }}"
},
"badge": {{ notification_status.unseen_count }},
"sound": "default",
"mutable-content": 1,
"content-available": 0,
"category": "stream.feeds"
},
"stream": {
"sender": "stream.feeds",
"type": "{{ event_type }}",
"fid": "{{ feed.fid }}",
"receiver_id": "{{ receiver.id }}"
}
}
}
Context Variables
Activity Feeds uses the Handlebars templating language to render push notification payloads for both Firebase and APN providers. This allows you to create dynamic, personalized notifications with conditional logic and data interpolation.
Activity Feeds provides the following variables in the template rendering context:
Name | Type | Description |
---|---|---|
event_type | string | The type of event (comment_added, activity_added, etc.) |
sender | object | The user who triggered the notification |
receiver | object | The user receiving the notification |
activity | object | The activity object containing id, text, and other data |
comment | object | Comment object (for comment-related notifications) |
reaction | object | Reaction object (for reaction-related notifications) Contains: type , id , etc. |
feed | object | The feed where the event occurred Contains: fid , etc. |
follow | object | Follow relationship object (for follow notifications) Contains: source_feed.FID , target_feed.FID , etc. |
notification_title | string | Generated title for notification feed updates |
notification_body | string | Generated body for notification feed updates |
notification_status | object | Contains unseen_count and unread_count |
aggregated_activity | object | Aggregated activity object for notification feeds Contains: group , etc. |
Template Conditional Variables
The templates also include conditional variables for better messaging:
Name | Type | Description |
---|---|---|
isMentionedInComment | boolean | True if user is mentioned in the comment |
isActivityOwner | boolean | True if user owns the activity being commented on |
isMentioned | boolean | True if user is mentioned in the activity |
Configuring Templates
Via API
The Upsert Push Template REST endpoint allows you to enable push notifications for specific event types and optionally define custom templates. Activity Feeds supports event types like comment_added
, activity_added
, activity_reaction
, and more.
Enabling Push with Default Template
{
"enable_push": true,
"event_type": "comment_added",
"push_provider_type": "firebase",
"push_provider_name": "firebase"
}
Enabling Push with Custom Template
{
"enable_push": true,
"event_type": "comment_added",
"push_provider_type": "firebase",
"push_provider_name": "firebase",
"template": "{\"data\":{\"sender\":\"stream.feeds\",\"type\":\"{{ event_type }}\",\"body\":\"{{ truncate comment.text 150 }}\",\"title\":\"{{ sender.name }} commented on your activity\",\"fid\":\"{{ feed.fid }}\",\"activity_id\":\"{{ activity.id }}\",\"comment_id\":\"{{ comment.id }}\",\"receiver_id\":\"{{ receiver.id }}\"},\"android\":{\"priority\":\"high\"},\"apns\":{\"payload\":{\"aps\":{\"alert\":{\"title\":\"{{ sender.name }} commented on your activity\",\"body\":\"{{ truncate comment.text 150 }}\"},\"sound\":\"default\",\"mutable-content\":1,\"content-available\":0,\"category\":\"stream.feeds\"}}}}"
}
Available Fields:
Field Name | Type | Description |
---|---|---|
enable_push | Boolean | Indicates whether push notifications are enabled for this event type. |
event_type | String (comment_added, activity_added, activity_reaction_added, comment_reaction_added, follow_created, notification_feed_updated) | The type of event used to apply the corresponding custom push configuration. |
push_provider_type | String | The type of push provider (firebase, apn) |
push_provider_name | String | The name of the configured push provider instance. Can be left empty if you’re not using multi-bundle support. |
template | String | The push notification template as a stringified JSON object. |
Example
// Enable push notifications for comment_added events with custom template
const response = await client.upsertPushTemplate({
enable_push: true,
event_type: "comment_added",
push_provider_type: "firebase",
push_provider_name: "firebase",
template: JSON.stringify({
data: {
sender: "stream.feeds",
type: "{{ event_type }}",
body: "{{ truncate comment.text 150 }}",
title: "{{ sender.name }} commented on your activity",
fid: "{{ feed.fid }}",
activity_id: "{{ activity.id }}",
comment_id: "{{ comment.id }}",
receiver_id: "{{ receiver.id }}",
},
android: {
priority: "high",
},
apns: {
payload: {
aps: {
alert: {
title: "{{ sender.name }} commented on your activity",
body: "{{ truncate comment.text 150 }}",
},
sound: "default",
"mutable-content": 1,
"content-available": 0,
category: "stream.feeds",
},
},
},
}),
});
Limitations
There are some limitations that Stream imposes on the push notification handlebars template to make sure no malformed payloads are being sent to push providers.
1: Custom Arrays Can’t Be Indexed
For example, given the context:
{
"sender": {
"name": "Bob",
"some_array": ["foo", "bar"]
}
}
And the template:
"title": {{ sender.some_array.[0] }}
The rendered payload will be:
"title": ""
2: Interpolating Whole Lists and Objects Isn’t Allowed
For example, given the context:
{
"sender": {
"name": "bob",
"some_array": ["foo", "bar"],
"address": {
"street": "willow str"
}
}
}
And the template:
"title": "{{ sender.some_array }} {{ sender.address }}"
The rendered payload will be:
"title": "[] {}"
3: Unquoted fields that aren’t in the context will be rendered as empty strings
For example, given the context:
{
"sender": {
"name": "bob"
}
}
And the template:
"title": {{ sender.missing_field }}
The rendered payload will be:
"title": ""
Advanced Use Cases
For advanced use cases (e.g. conditional rendering, data validation, etc), Stream provides handlebars helper functions that can be used in Activity Feeds templates.
Helper Functions
name | type | description |
---|---|---|
json | function | renders passed parameter as JSON (e.g {“activity”:{{{ json activity }}}} ) |
each | function | For loop. Use this to access the current variable, @index for the current index and @first and @last as convenience booleans |
if | function | If function. Tests trueness of given parameter. Supports else statement. (e.g {{#if sender.name}}{{ sender.name }}{{/if}} ) |
unless | function | Unless function. Tests falseness of given parameter. Supports else statement. (e.g {{#unless sender.name}}Missing name{{/unless}} ) |
equal | function | Equality check function. Tests equality of the given 2 parameters. Supports else statement. (e.g {{#equal event_type “comment_added” }}Comment{{else}}Other{{/equal}} ) |
unequal | function | Inequality check function. Tests inequality of the given 2 parameters. Supports else statement. |
ifLt | function | If less than. Supports else statement. |
ifLte | function | If less than or equal. Supports else statement. |
ifGt | function | If greater than. Supports else statement. |
ifGte | function | If greater than or equal. Supports else statement. |
truncate | function | Truncate given text to given length (e.g {{ truncate activity.text 150 }}) |
Function Parameters
These helper functions can be combined to create sophisticated conditional logic in your notification templates. They are particularly useful for:
- Conditional messaging based on event types
- Data validation and fallback values
- Text truncation for different screen sizes
- Complex logic for personalized notifications
Examples
Example 1: Conditional Event Type Messaging
{
"aps": {
"alert": {
"title": "{{#equal event_type 'comment_added'}}New Comment{{else}}{{#equal event_type 'activity_reaction'}}New Reaction{{else}}Activity Update{{/equal}}{{/equal}}",
"body": "{{ sender.name }}: {{#if comment}}{{ truncate comment.text 150 }}{{else}}{{ truncate activity.text 150 }}{{/if}}"
}
}
}
Example 2: Advanced Conditional Logic
{
"data": {
"title": "{{#if isMentionedInComment}}{{ sender.name }} mentioned you{{else}}{{#if isActivityOwner}}{{ sender.name }} commented on your activity{{else}}{{ sender.name }} commented{{/if}}{{/if}}",
"body": "{{#unless comment.text}}No comment text{{else}}{{ truncate comment.text 150 }}{{/unless}}"
}
}