Push Templates

Last Edit: Nov 27 2020

For both Firebase and APN, the payload that is being sent is rendered using the handlebars templating language, to ensure full configurability for your app.

Stream provides the following variables in the template rendering context:

Context Variables

Name Type Description
channel object Channel object. You can access the channel name and any other custom field you have defined for this channel
sender object Sender object. You can access the user name, id or any other custom field you have defined for the user
receiver object Receiver object. You can access the user name, id or any other custom field you have defined for the user
message object Message object. You can access the text of the message (or a preview of it if the message is too large) or any other custom field you have defined for the message
members array Channel members. You can access the user name, id and any other custom field of each member (i.e. excluding sender)
otherMembers array Like members but the user who will be receiving the notification is excluded (i.e. excluding sender and receiver)
unread_count integer Number of unread messages
unread_channels integer Number of unread channels for this user

Defaults

When editing APN/Firebase settings, if you leave the notification_template or data_template field empty, default templates will be used.

APN default:


{
    "aps" : {
        "alert" : {
            "title" : "{{ sender.name }} @ {{ channel.name }}",
            "body" : "{{ message.text }}"
        },
        "badge": {{ unread_count }},
        "category" : "NEW_MESSAGE"
    }
}
                    

Firebase default notification template:


{
    "title": "{{ sender.name }} @ {{ channel.name }}",
    "body": "{{ message.text }}",
    "click_action": "OPEN_ACTIVITY_1",
    "sound": "default"
}
                    

Firebase default data template:


{
    "sender": "{{ sender.id }}",
    "channel": {
        "type": "{{ channel.type }}",
        "id": "{{ channel.id }}"
    },
    "message": "{{ message.id }}"
}
                    

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. A list of channel members in the notification title, conditional rendering, etc), Stream provides some handlebars helper functions.

Helper Functions

name type description
implodemembers function takes the list of channel members and implodes it into a single string, using a custom limit, separator and suffix.
json function renders passed parameter as JSON (e.g <code>&lcub;&quot;channel&quot;:&lcub;&lcub;&lcub; json channel &rcub;&rcub;&rcub;&rcub;</code>)
each function For loop. Use <code>this</code> to access the current variable, <code>@index</code> for the current index and <code>@first</code> and <code>@last</code> as convenience booleans to determine if the iteration is at its first/last element
if function If function. Tests trueness of given parameter. Supports else statement. (e.g <code>&lcub;&lcub;#if sender.name&rcub;&rcub;&lcub;&lcub; sender.name &rcub;&rcub;&lcub;&lcub;/if&rcub;&rcub;</code>)
unless function Unless function. Tests falseness of given parameter. Supports else statement. (e.g <code>&lcub;&lcub;#unless sender.name&rcub;&rcub;Missing name&lcub;&lcub;/unless&rcub;&rcub;</code>)
equal function Equality check function. Tests equality of the given 2 parameters. Supports else statement. (e.g <code>&lcub;&lcub;#equal channel.type &quot;messaging&quot; &rcub;&rcub;This is the messaging channel&lcub;&lcub;else&rcub;&rcub;This is another channel&lcub;&lcub;/equal&rcub;&rcub; </code>)
unequal function Inequality check function. Tests inequality of the given 2 parameters. Supports else statement. (e.g <code>&lcub;&lcub;#unequal channel.type &quot;messaging&quot; &rcub;&rcub;This is another channel&lcub;&lcub;else&rcub;&rcub;This is the messaging channel&lcub;&lcub;/unequal&rcub;&rcub; </code>)
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.
remainder function Calculates the difference between the length of an array and an integer (e.g <code>&lcub;&lcub;remainder otherMembers 2&rcub;&rcub;</code>

Most of the functions above are straight forward, except for implodeMembers, which will be detailed further.

The full function signature is: {{implodeMembers otherMembers|members [limit=] [separator=] [nameField=] [suffixFmt=]}}

Function Parameters

name type description default
otherMembers | members array Which member array to implode
limit integer How many member names to show before adding the suffix 3
nameField string Field name from which field to retrieve the member's name. <b>Note:</b> does not support nesting name
separator string Separator to use for channel members ,
suffixFmt string Format string to use for the suffix. <b>Note:</b> only %d is allowed for formatting and %d other(s)

Examples

Let's put these helpers to use in a few examples:

Example 1

Rendering channel members in the notification title. Each member's name is stored in the fullName field.

What we want to achieve:


{
    "aps": {
        "alert": {
            "title": "Bob Jones, Jessica Wright, Tom Hadle and 4 other(s)",
            "body": "Bob Jones: Hello there fellow channel members"
        },
        "badge": 0
    }
}
                    

How we will achieve it: using implodeMembers with a custom name field (leaving others empty so that defaults will be used):


{
    "aps": {
        "alert": {
            "title": "{{implodeMembers otherMembers nameField="fullName"}}",
            "body": "{{ sender.fullName }}: {{ message.text }}"
        },
        "badge": {{ unread_count }}
    }
}
                    

Example 2

Rendering channel members in the notification title. Each member's name is stored in the nested details.name field.

What we want to achieve:


{
    "aps": {
        "alert": {
            "title": "Bob Jones, Jessica Wright, Tom Hadle and 4 other(s)",
            "body": "Bob Jones: Hello there fellow channel members"
        },
        "badge": 0
    }
}
                    

How we will achieve it: since implodeMembers doesn't support nested fields, we need to use a bunch of helpers such as each, ifLte. Note how the use of ~ will trim the whitespaces so that the title in rendered in a single row:


{
    "aps": {
        "alert": {
            "title": "
            {{~#each otherMembers}}
                {{#ifLte @index 2}}
                    {{~this.details.name}}{{#ifLt @index 2 }}, {{/ifLt~}}
                {{~else if @last~}}
                    {{{ " " }}} and {{remainder otherMembers 3}} other(s)
                {{~/ifLte~}}
            {{/each~}}",
            "body": "{{ sender.details.name }}: {{ message.text }}"
        },
        "badge": {{ unread_count }}
    }
}