Personalization templates
Insert user profile fields, attributes and notification data into titles, bodies and action labels.
Titles, bodies and action labels can contain {{ … }} placeholders. They are filled in for
each user when their feed loads, so one broadcast greets every user by name. Templates
need the Growth plan and up (see plan features).
{
"audience": { "type": "all" },
"contents": {
"en": {
"title": "Hi {{user.name | there}}, your {{user.attributes.plan | trial}} plan renews soon",
"body": "Invoice **{{data.invoiceNumber}}** will be charged on {{data.dueDate}}.",
"actionLabel": "Review plan"
}
},
"data": { "invoiceNumber": "INV-1042", "dueDate": "1 November" }
}For Ada on the Pro plan this renders as "Hi Ada Lovelace, your pro plan renews soon"; for a user without a name or plan, "Hi there, your trial plan renews soon".
Syntax
{{ path }}
{{ path | fallback text }}- Spaces inside the braces are optional:
{{user.name}}and{{ user.name | there }}are the same. - The fallback is used when the value is missing,
nullor an empty string. Without a fallback, a missing value renders as nothing. - Lists render comma-separated (
a, b, c). Objects can't be rendered and use the fallback. - Numbers and booleans render as text (
42,true). - Paths may contain letters, digits,
_,-and., so{{user.attributes.plan-name}}works.
Available values
| Path | Value |
|---|---|
user.id | Your user id (the userId you passed to the SDK or API) |
user.name | Profile name |
user.email | Profile email |
user.locale | Profile locale |
user.attributes.<key> | A custom attribute, e.g. user.attributes.plan |
data.<key> | A value from the notification's data, e.g. data.invoiceNumber. Nested paths like data.order.id work. |
Anything else (including user.timezone and user.avatarUrl) is treated as missing.
Where templates apply
Templates are rendered in title, body and actionLabel, in every locale. They are
not rendered in actionUrl, imageUrl, category or data; build per-user URLs on
your side and send them to that user (a users audience) instead.
Because rendering happens at read time, the latest profile wins: if a user changes their name, older notifications show the new name too.
Limits
- Attribute keys with characters other than letters, digits,
_and-(such as spaces or.) can't be referenced:.separates path segments, so{{user.attributes.a.b}}doesn't find an attribute nameda.b. - The fallback can't contain
}. - Length limits (title 200, body 2,000, action label 60 characters) apply to the template as sent, not to the rendered text.
Safety
- Templates have no logic, loops or function calls, and values are inserted as plain text: there is no HTML, so a value can't inject markup into the SDK's UI.
- Bodies support a small Markdown subset (bold, italic, links). Values inserted into a
body are Markdown-escaped:
\,*,_,[,],(and)get a backslash, and the SDKs render\xas a literalx. So a name like[click](https://evil.example)shows up as text, never as a link. Your own Markdown in the template, and fallback text you write, are kept as Markdown. - Titles and action labels are plain text (no Markdown), so values there are inserted unchanged.
- Profile fields and attributes can be set by the user's own SDK, so a user can only ever change what they see, not what other users see.
- Everything in
datais delivered to the client as-is. Don't put secrets in it.
Note
Want to see the result? GET /v1/project-users/{userId}/feed returns a user's feed exactly
as the SDK receives it, templates rendered.