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, null or 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

PathValue
user.idYour user id (the userId you passed to the SDK or API)
user.nameProfile name
user.emailProfile email
user.localeProfile 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 named a.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 \x as a literal x. 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 data is 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.