> For the complete documentation index, see [llms.txt](https://synap.ac/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://synap.ac/docs/platform/advanced-features/dynamic-content.md).

# Dynamic content

Dynamic content lets one piece of authored content adapt to the person viewing it and the context in which Synap renders it. It uses text variables for substitution and a small set of logic blocks for conditions and iteration.

> **Important:** The syntax is inspired by Handlebars, but it is Synap's own deliberately limited implementation. Only the syntax documented on this page is supported.

### Where dynamic content is available

When an editor supports dynamic content, its toolbar contains **Variables** and **Logic** controls. The Variables menu lists values available in that particular context. The Logic menu inserts a starting template for a condition or loop.

Confirmed uses include:

<figure><img src="/files/HyG7Lqrqan0iccvdM1bk" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/XSfdAS5MGsE4ilA8Fa7B" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/h7MEHdMlfgawl3C6r8Tb" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/EQmJetskOVoD3zQfLNRW" alt=""><figcaption></figcaption></figure>

| Area         | Example content                                                            | Additional context                                    |
| ------------ | -------------------------------------------------------------------------- | ----------------------------------------------------- |
| Notes        | Note body                                                                  | User, portal and configured attributes                |
| Questions    | Lead-in, stem, options, feedback and marking information                   | User and portal; availability varies by field         |
| Quizzes      | Result messages                                                            | Attempt ID where offered by the editor                |
| Exams        | Pre-exam instructions, exam instructions and legal terms                   | Exam, attempt and attempt attributes                  |
| Exam results | Hidden, pending and custom result messages                                 | Exam, attempt and attempt attributes                  |
| Emails       | Welcome, invitation, generated-account and administrator-composed messages | Recipient, portal, sender and sometimes attempt links |
| Sub-portals  | Configurable email content                                                 | User, portal and sub-portal context                   |

The available variables are context-sensitive. A variable shown in one editor is not necessarily available in another.

### Syntax at a glance

| Purpose                           | Syntax                                                                 |
| --------------------------------- | ---------------------------------------------------------------------- |
| Insert a value                    | `{{user.firstName}}`                                                   |
| Insert a value with fallback text | `{{user.firstName \| "there"}}`                                        |
| Test truthiness                   | `{{#if user.firstName}}...{{/if}}`                                     |
| Compare a value                   | `{{#if user.attr.region "Europe"}}...{{/if}}`                          |
| Add an alternative                | `{{#if variable}}...{{else}}...{{/if}}`                                |
| Add several alternatives          | `{{#if variable "a"}}...{{else if variable "b"}}...{{else}}...{{/if}}` |
| Invert a test                     | `{{#unless variable}}...{{/unless}}`                                   |
| Iterate an array                  | `{{#each user.attr.products}}{{@this}}{{/each}}`                       |
| Current array item                | `{{@this}}`                                                            |
| One-based item number             | `{{@num}}`                                                             |

The following general Handlebars-style features are **not** supported:

* helpers or functions;
* arithmetic;
* greater-than, less-than or range comparisons;
* `and` and `or` expressions;
* parentheses or compound expressions;
* formatting filters;
* reliable iteration over object properties.

Build complex rules from nested `#if`, `else if`, `else` and `#unless` blocks, or move the rule into the underlying user or attempt attribute.

### Text variables

A text variable is a path enclosed by two opening and two closing curly brackets:

```handlebars
{{user.firstName}}
```

When Synap renders the content, it looks up the path in the current context and replaces the token with the corresponding value.

```handlebars
Hello {{user.firstName}}.
```

For a user whose first name is `Sam`, the rendered output is:

```
Hello Sam.
```

Whitespace immediately inside the brackets is ignored, so these are equivalent:

```handlebars
{{user.firstName}}
{{ user.firstName }}
```

Variable names and attribute identifiers are case-sensitive. Insert variables from the editor menu where possible rather than typing their paths manually.

#### Nested values and array indexes

Use a dot to move through nested values:

```handlebars
{{user.subPortal.name}}
{{user.attr.department}}
{{attempt.attr.candidate-number}}
```

Use square brackets to select an item from an array. Array indexes begin at `0`:

```handlebars
{{user.attr.products[0]}}
```

This returns the first item in `products`.

#### Fallback text

Add a pipe and fallback text when a value may not exist:

```handlebars
Hello {{user.firstName | "there"}}.
```

If `user.firstName` is `Sam`, the output is `Hello Sam.`. If it is unavailable, the output is `Hello there.`.

Spacing around the pipe is optional. Straight, single and typographic quotation marks are accepted around fallback text, although straight double quotation marks are the clearest convention:

```handlebars
{{user.firstName|"there"}}
{{ user.firstName | "there" }}
```

If a variable cannot be resolved and has no fallback, Synap removes the token:

```handlebars
Hello {{user.unknownValue}}.
```

renders as:

```
Hello .
```

> **Use fallbacks in prose.** Removing an unresolved token can leave awkward punctuation or incomplete sentences.

### Variable sources

The Variables menu is the authority for the current editor. The following paths are the standard sources used throughout Synap.

#### User variables

| Value           | Syntax                    |
| --------------- | ------------------------- |
| User ID         | `{{user.id}}`             |
| First name      | `{{user.firstName}}`      |
| Last name       | `{{user.lastName}}`       |
| Full name       | `{{user.fullName}}`       |
| Email address   | `{{user.email}}`          |
| Locale          | `{{user.locale}}`         |
| Time zone       | `{{user.timeZone}}`       |
| Sub-portal ID   | `{{user.subPortal.id}}`   |
| Sub-portal name | `{{user.subPortal.name}}` |

Configured user attributes appear below the user group and use their custom identifier:

```handlebars
{{user.attr.department}}
{{user.attr.employee-number}}
```

Configured sub-portal attributes use:

```handlebars
{{user.subPortal.attr.region}}
```

#### Portal variables

| Value       | Syntax            |
| ----------- | ----------------- |
| Portal ID   | `{{portal.id}}`   |
| Portal name | `{{portal.name}}` |
| Portal URL  | `{{portal.url}}`  |

For a sub-portal user or recipient, the portal name and URL may resolve to the relevant sub-portal context.

#### Context-specific variables

Some editors add more groups:

| Context                  | Examples                                                                                       |
| ------------------------ | ---------------------------------------------------------------------------------------------- |
| Exam                     | `{{exam.id}}`                                                                                  |
| Attempt                  | `{{attempt.id}}`, `{{attempt.attr.candidate-number}}`                                          |
| Email sender             | `{{sender.firstName}}`, `{{sender.fullName}}`, `{{sender.email}}`                              |
| Exam or assignment email | `{{attempt.title}}`, `{{attempt.startUrl}}`, `{{attempt.startUrlAuthenticated}}` where offered |

Invitation editors use the details being entered for the future recipient. The familiar paths remain `{{user.firstName}}`, `{{user.lastName}}`, `{{user.fullName}}` and `{{user.email}}`, even though the user account may not yet exist.

> **Security:** Session tokens and authenticated start links can grant access as a user. Only use variables such as `user.token` or `attempt.startUrlAuthenticated` in the specific transactional context for which they are provided. Do not place them in shared notes, question content, reusable public content or logs.

### Value types

Dynamic content can receive strings, numbers, booleans, dates, arrays and objects. How a value behaves depends on whether it is substituted directly, compared in a condition or iterated.

| Type    | Direct substitution                                      | Conditions                                         | Recommended approach                                        |
| ------- | -------------------------------------------------------- | -------------------------------------------------- | ----------------------------------------------------------- |
| String  | Rendered as text                                         | Compare with a quoted string                       | Use directly or provide a fallback                          |
| Number  | Rendered as text, except `0`                             | Compare with an unquoted number                    | Use a condition when zero is meaningful                     |
| Boolean | Does not render as visible `true` or `false`             | Compare with `true` or `false`, or test truthiness | Use `#if` or `#unless`                                      |
| Date    | Rendered using the runtime locale's date and time format | Use only as a truthy/falsy presence test           | Avoid exact date comparisons and display-format assumptions |
| Array   | Renders as `[Array]` when used directly                  | Non-empty arrays are truthy                        | Select an index or use `#each`                              |
| Object  | Renders as `[Object]` when used directly                 | Objects are truthy                                 | Address a nested property                                   |

#### Falsy values

During direct substitution, Synap treats the following values as unavailable:

* `0`
* `false`
* an empty string
* `null` or an undefined value

They therefore resolve to the fallback, or to an empty string if no fallback is present:

```handlebars
{{attempt.attr.score | "Not available"}}
```

If the score is `0`, this renders `Not available`. Where zero and false are meaningful values, use conditional content rather than relying on direct substitution.

### Conditional content

Condition blocks choose which content to render. The supported operators are `#if`, `else if`, `else` and `#unless`.

#### Test whether a value exists or is truthy

```handlebars
{{#if user.firstName}}
  Hello {{user.firstName}}.
{{else}}
  Hello there.
{{/if}}
```

The first branch is used when `user.firstName` is truthy. Otherwise the `else` branch is used.

JavaScript-style truthiness applies. Empty strings, zero, false, null and unavailable paths are falsy. Non-empty strings, non-zero numbers, arrays and objects are truthy.

#### Compare a value

Place the comparison value after the variable path:

```handlebars
{{#if user.attr.department "Compliance"}}
  Complete the annual compliance assessment.
{{/if}}
```

Comparisons use strict equality. The value and its type must match.

| Value to compare | Syntax                                | Example                                                          |
| ---------------- | ------------------------------------- | ---------------------------------------------------------------- |
| String           | Quote the value                       | `{{#if user.attr.region "Europe"}}`                              |
| Number           | Do not quote it                       | `{{#if attempt.attr.score 100}}`                                 |
| Boolean          | Use `true` or `false` without quotes  | `{{#if user.attr.manager true}}`                                 |
| Date             | Exact date comparison is not reliable | Test whether it exists, or use a separate text/boolean attribute |

Unquoted text is not treated as a string. For example, use `"Compliance"`, not `Compliance`.

#### Multiple branches

```handlebars
{{#if user.attr.level "Advanced"}}
  Your advanced pathway is ready.
{{else if user.attr.level "Intermediate"}}
  Continue with the intermediate pathway.
{{else}}
  Start with the foundation pathway.
{{/if}}
```

Synap renders only the first matching branch. `else` is optional and must be the final branch.

#### Unless

`#unless` is the inverse of `#if`:

```handlebars
{{#unless user.attr.manager true}}
  Manager approval is required.
{{/unless}}
```

Without a comparison value, it renders when the variable is unavailable or falsy:

```handlebars
{{#unless user.attr.employee-number}}
  Contact support to add your employee number.
{{/unless}}
```

#### Nested conditions

Conditions can be nested:

```handlebars
{{#if user.attr.department "Clinical"}}
  {{#if user.attr.level "Advanced"}}
    Open the advanced clinical assessment.
  {{else}}
    Open the standard clinical assessment.
  {{/if}}
{{else}}
  Open your assigned assessment.
{{/if}}
```

Keep nesting shallow. Deeply nested content is difficult to review and easy to break when edited visually.

### Arrays and iteration

Use `#each` to repeat content for every item in an array:

```handlebars
{{#each user.attr.products}}
  <p>{{@num}}. {{@this}}</p>
{{/each}}
```

Within the loop:

* `{{@this}}` is the current array item.
* `{{@num}}` is its one-based position: `1`, `2`, `3` and so on.

For an array containing `alpha`, `beta` and `gamma`, the output is:

```
1. alpha
2. beta
3. gamma
```

`#each` is intended for arrays of simple values such as text. Objects render as `[Object]`; iteration does not provide a reliable general-purpose object-property syntax. If you need one known item, address it by index instead:

```handlebars
{{user.attr.products[0]}}
```

Use one `{{@this}}` token per repeated line or block. Avoid placing multiple `{{@this}}` tokens on the same line.

### Variables in links and HTML

Variables are processed inside HTML attributes as well as visible text. This allows context-sensitive links:

```html
<a href="{{portal.url}}/account">Open your account</a>
```

Only place trusted Synap-provided values in URLs. A fallback is useful if an optional value forms part of a link.

Content inside HTML `<code>` and `<math>` blocks is deliberately left unchanged. This prevents programming examples and mathematical notation that use curly brackets from being interpreted as dynamic content:

```html
<code>{{user.firstName}}</code>
```

The token remains visible exactly as written.

### Authoring and testing

1. Open a supported rich-text editor.
2. Place the cursor where the value should appear.
3. Select **Variables**, then choose a variable from the relevant group.
4. Select **Logic** to insert an `#if`, `#if/else`, `#if/elseif/else`, `#unless` or `#each` template.
5. Replace the template's example path and value without removing its opening or closing block.
6. Preview the content in the same context in which learners will receive it.
7. Test at least one record for every expected branch, including a user with missing optional attributes.

The administrator viewing an editor may not have the same data as the eventual recipient. For emails, exam instructions and results, the final values are normally resolved for the recipient or attempt when Synap sends or displays the content.

> **Testing checklist:** Test a typical user, missing data, false and zero values, each conditional branch, sub-portal recipients, and any link containing a variable.

### Common problems

| Symptom                               | Likely cause                                                              | What to check                                                       |
| ------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| Blank space or broken sentence        | The path is unavailable and has no fallback                               | Add fallback text and confirm the path in the Variables menu        |
| Fallback appears for a valid value    | The value is `0`, `false` or an empty string                              | Use conditional logic when that value is meaningful                 |
| Condition never matches               | Wrong type, case or missing quotation marks                               | Quote strings; leave numbers and booleans unquoted                  |
| `[Array]` appears                     | An array was inserted directly                                            | Use an index or `#each`                                             |
| `[Object]` appears                    | An object was inserted directly                                           | Select a nested property                                            |
| Token is still visible                | It is inside a code/math block, or the surface does not process variables | Move it outside the block and confirm the editor supports Variables |
| Date format differs                   | Dates use the locale of the rendering environment                         | Avoid prose that depends on one exact date format                   |
| Different users see different results | The content is resolving correctly against their data                     | Confirm the intended variable source and privacy expectations       |

### Worked recipes

#### Personalised greeting with a safe fallback

```handlebars
Hello {{user.firstName | "there"}}, welcome to {{portal.name}}.
```

#### Different instructions by attribute

```handlebars
{{#if user.attr.region "UK"}}
  Follow the UK policy linked below.
{{else if user.attr.region "EU"}}
  Follow the EU policy linked below.
{{else}}
  Contact your administrator for the policy that applies to you.
{{/if}}
```

#### Display an optional identifier

```handlebars
{{#if attempt.attr.candidate-number}}
  Candidate number: {{attempt.attr.candidate-number}}
{{else}}
  Your candidate number has not been assigned yet.
{{/if}}
```

#### Number a list stored in a multi-value attribute

```handlebars
{{#each user.attr.products}}
  <p>{{@num}}. {{@this}}</p>
{{/each}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://synap.ac/docs/platform/advanced-features/dynamic-content.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
