> ## Documentation Index
> Fetch the complete documentation index at: https://www.courier.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Control Flow

> Use conditional rendering, loops, element references, and channel-specific content in your Elemental notifications. Control flow properties enable dynamic, data-driven templates.

## Overview

Courier Elemental supports four control flow properties that enable dynamic, data-driven notification templates:

* **`if`** - Conditionally render elements based on data or conditions
* **`loop`** - Repeat elements for each item in an array
* **`ref`** - Reference elements to check their visibility or properties
* **`channels`** - Show elements only on specific channels

All control flow properties are optional and can be combined on the same element. These properties are available on all Elemental elements.

<Info>
  Control flow properties are evaluated at render time using Handlebars expressions. You can access data from `message.data`, `message.to.data`, and other message context.
</Info>

## If

The `if` property conditionally renders an element based on a Handlebars expression. If the expression evaluates to a truthy value, the element is rendered; otherwise, it's skipped.

**When to use:**

* Show different content based on user type, subscription status, or feature flags
* Display elements only when certain data exists
* Create personalized experiences based on user context
* Hide elements that aren't relevant to the current recipient

**Applies to**: All Elemental elements

### Basic Example

<CodeGroup>
  ```json icon="code" theme={null}
  {
    "type": "text",
    "content": "Welcome, premium member!",
    "if": "data.user_tier === 'premium'"
  }
  ```

  ```javascript Node.js icon="node-js" lines theme={null}
  const { CourierClient } = require("@trycourier/courier");

  const courier = new CourierClient({
    authorizationToken: process.env.COURIER_AUTH_TOKEN,
  });

  await courier.send({
    message: {
      to: { email: "user@example.com" },
      content: {
        version: "2022-01-01",
        elements: [
          {
            type: "text",
            content: "Welcome, premium member!",
            if: "data.user_tier === 'premium'",
          },
        ],
      },
      data: {
        user_tier: "premium",
      },
    },
  });
  ```

  ```python Python icon="python" lines theme={null}
  import os
  from trycourier import Courier

  client = Courier(auth_token=os.environ["COURIER_AUTH_TOKEN"])

  client.send_message(
      message={
          "to": {"email": "user@example.com"},
          "content": {
              "version": "2022-01-01",
              "elements": [
                  {
                      "type": "text",
                      "content": "Welcome, premium member!",
                      "if": "data.user_tier === 'premium'",
                  }
              ],
          },
          "data": {"user_tier": "premium"},
      }
  )
  ```
</CodeGroup>

### Realistic Examples

**Conditional welcome message based on user type:**

```json theme={null}
{
  "version": "2022-01-01",
  "elements": [
    {
      "type": "text",
      "content": "Welcome back, {{first_name}}!",
      "if": "data.is_returning_user"
    },
    {
      "type": "text",
      "content": "Welcome to {{company_name}}, {{first_name}}!",
      "if": "!data.is_returning_user"
    },
    {
      "type": "action",
      "content": "Upgrade to Premium",
      "href": "https://example.com/upgrade",
      "if": "data.user_tier !== 'premium'"
    }
  ]
}
```

**Show elements only when data exists:**

```json theme={null}
{
  "type": "group",
  "if": "data.items && data.items.length > 0",
  "elements": [
    {
      "type": "text",
      "content": "Your order contains {{data.items.length}} item(s)"
    }
  ]
}
```

### Structured conditions

In addition to a string expression, the `if` field accepts a **structured condition array** — an array of one or more condition groups evaluated at render time. The element renders if **any** group matches (groups are OR'd together). Within each group, conditions are combined using the group's `logical_operator`.

**Supported property namespaces** (dot-paths only):

| Namespace   | Description                                | Example                     |
| ----------- | ------------------------------------------ | --------------------------- |
| `data.*`    | Values from the send `data` payload        | `data.order_total`          |
| `profile.*` | Recipient profile fields                   | `profile.plan`              |
| `refs.*`    | Visibility of another element by its `ref` | `refs.promo_banner.visible` |

**Supported operators:**

| Operator                                                                     | Type   | `value` required |
| ---------------------------------------------------------------------------- | ------ | ---------------- |
| `equals`, `not_equals`                                                       | binary | yes              |
| `greater_than`, `less_than`, `greater_than_or_equals`, `less_than_or_equals` | binary | yes              |
| `contains`, `not_contains`                                                   | binary | yes              |
| `is_empty`, `is_not_empty`                                                   | unary  | no               |

**Single group (all conditions AND'd):**

```json theme={null}
{
  "type": "text",
  "content": "You qualify for the enterprise plan.",
  "if": [
    {
      "logical_operator": "and",
      "conditions": [
        { "property": "data.account_type", "operator": "equals", "value": "enterprise" },
        { "property": "profile.email_verified", "operator": "equals", "value": "true" }
      ]
    }
  ]
}
```

**Multiple groups (OR between groups):**

```json theme={null}
{
  "type": "text",
  "content": "Upgrade available!",
  "if": [
    {
      "logical_operator": "and",
      "conditions": [{ "property": "data.plan", "operator": "equals", "value": "free" }]
    },
    {
      "logical_operator": "and",
      "conditions": [{ "property": "data.plan", "operator": "equals", "value": "starter" }]
    }
  ]
}
```

**Unary operator (no `value` needed):**

```json theme={null}
{
  "type": "text",
  "content": "No promo code applied.",
  "if": [
    {
      "logical_operator": "and",
      "conditions": [{ "property": "data.promo_code", "operator": "is_empty" }]
    }
  ]
}
```

**Using `refs` to check element visibility:**

```json theme={null}
{
  "type": "text",
  "content": "Here is what you unlocked:",
  "if": [
    {
      "logical_operator": "and",
      "conditions": [{ "property": "refs.promo_block.visible", "operator": "equals", "value": "true" }]
    }
  ]
}
```

<Info>
  String `if` and structured `if` are mutually exclusive on the same element — use one or the other. String expressions remain fully supported for full flexibility.
</Info>

## Ref

The `ref` property names an element so it can be referenced by other elements. Referenced elements provide access to their properties and a `visible` property that indicates whether the element was rendered.

**When to use:**

* Check if another element was rendered before showing related content
* Create dependencies between elements
* Build complex conditional logic based on element visibility
* Access element properties from other elements

**Applies to**: All Elemental elements

**Note**: An element cannot reference another element that is defined after it in the elements array. References must point to elements that appear earlier in the template.

### Basic Example

<CodeGroup>
  ```json icon="code" theme={null}
  {
    "type": "text",
    "content": "Hello, {{first_name}}",
    "ref": "greeting"
  },
  {
    "type": "text",
    "content": "This shows if greeting is visible",
    "if": "refs.greeting.visible"
  }
  ```

  ```javascript Node.js icon="node-js" lines theme={null}
  await courier.send({
    message: {
      to: { email: "user@example.com" },
      content: {
        version: "2022-01-01",
        elements: [
          {
            type: "text",
            content: "Hello, {{first_name}}",
            ref: "greeting",
          },
          {
            type: "text",
            content: "This shows if greeting is visible",
            if: "refs.greeting.visible",
          },
        ],
      },
      data: { first_name: "Alex" },
    },
  });
  ```

  ```python Python icon="python" lines theme={null}
  client.send_message(
      message={
          "to": {"email": "user@example.com"},
          "content": {
              "version": "2022-01-01",
              "elements": [
                  {
                      "type": "text",
                      "content": "Hello, {{first_name}}",
                      "ref": "greeting",
                  },
                  {
                      "type": "text",
                      "content": "This shows if greeting is visible",
                      "if": "refs.greeting.visible",
                  },
              ],
          },
          "data": {"first_name": "Alex"},
      }
  )
  ```
</CodeGroup>

### Realistic Example

**Show follow-up content only if initial element is visible:**

```json theme={null}
{
  "version": "2022-01-01",
  "elements": [
    {
      "type": "text",
      "content": "Special offer for premium members!",
      "ref": "premium_offer",
      "if": "data.user_tier === 'premium'"
    },
    {
      "type": "action",
      "content": "Claim Offer",
      "href": "https://example.com/claim",
      "if": "refs.premium_offer.visible"
    }
  ]
}
```

## Loop

The `loop` property renders an element multiple times, once for each item in an iterable data source (typically an array).

**When to use:**

* Display lists of products, orders, notifications, or other array data
* Create dynamic content that adapts to variable-length data
* Build repeating patterns like product cards or notification items
* Iterate over nested data structures

**Applies to**: All Elemental elements

**Loop variables:**

* `$.item` - The current item in the iteration
* `$.index` - The zero-based index of the current iteration

### Basic Example

<CodeGroup>
  ```json icon="code" theme={null}
  {
    "type": "text",
    "content": "* {{$.item.name}} - {{$.item.price}}",
    "loop": "data.products"
  }
  ```

  ```javascript Node.js icon="node-js" lines theme={null}
  await courier.send({
    message: {
      to: { email: "user@example.com" },
      content: {
        version: "2022-01-01",
        elements: [
          {
            type: "text",
            content: "* {{$.item.name}} - {{$.item.price}}",
            loop: "data.products",
          },
        ],
      },
      data: {
        products: [
          { name: "Mouse", price: "$10" },
          { name: "Keyboard", price: "$20" },
        ],
      },
    },
  });
  ```

  ```python Python icon="python" lines theme={null}
  client.send_message(
      message={
          "to": {"email": "user@example.com"},
          "content": {
              "version": "2022-01-01",
              "elements": [
                  {
                      "type": "text",
                      "content": "* {{$.item.name}} - {{$.item.price}}",
                      "loop": "data.products",
                  }
              ],
          },
          "data": {
              "products": [
                  {"name": "Mouse", "price": "$10"},
                  {"name": "Keyboard", "price": "$20"},
              ],
          },
      }
  )
  ```
</CodeGroup>

### Realistic Examples

**Product list with nested data:**

```json theme={null}
{
  "version": "2022-01-01",
  "elements": [
    {
      "type": "group",
      "loop": "data.products",
      "elements": [
        {
          "type": "text",
          "content": "{{$.item.name}}",
          "text_style": "h2"
        },
        {
          "type": "text",
          "content": "Price: {{$.item.price}}"
        },
        {
          "type": "image",
          "src": "{{$.item.image_url}}",
          "alt_text": "{{$.item.name}}"
        },
        {
          "type": "action",
          "content": "View Details",
          "href": "https://example.com/products/{{$.item.id}}"
        },
        {
          "type": "divider"
        }
      ]
    }
  ]
}
```

**Using `$.index` for item numbering:**

<Tip>
  When using `$.index` in loops, you can display a 1-based item number by using the `add` [Handlebars helper](/platform/content/template-designer/handlebars-helpers#add):

  ```handlebars theme={null}
  Item {{add $.index 1}}: {{$.item.name}}
  ```

  This will output "Item 1", "Item 2", etc., instead of starting from zero.
</Tip>

```json theme={null}
{
  "type": "text",
  "content": "Item {{add $.index 1}}: {{$.item.name}} - {{$.item.price}}",
  "loop": "data.products"
}
```

## Channels

The `channels` property selectively renders an element based on the current notification channel. This allows you to show different content for email, SMS, push, and other channels.

**When to use:**

* Show detailed content in email, concise content in SMS
* Display channel-specific formatting or elements
* Customize content per channel while maintaining a single template
* Hide elements that don't work well on certain channels

**Applies to**: All Elemental elements

**Valid channels**: `email`, `push`, `direct_message`, `sms`, or provider-specific channels like `slack`, `discord`, etc.

<Info>
  For more advanced channel customization, you can also use [Channel elements](/platform/content/elemental/elements/channel) to define completely different content structures per channel.
</Info>

### Basic Example

<CodeGroup>
  ```json icon="code" theme={null}
  {
    "type": "text",
    "content": "This only appears in email and push",
    "channels": ["email", "push"]
  }
  ```

  ```javascript Node.js icon="node-js" lines theme={null}
  await courier.send({
    message: {
      to: { email: "user@example.com" },
      content: {
        version: "2022-01-01",
        elements: [
          {
            type: "text",
            content: "This only appears in email and push",
            channels: ["email", "push"],
          },
        ],
      },
    },
  });
  ```

  ```python Python icon="python" lines theme={null}
  client.send_message(
      message={
          "to": {"email": "user@example.com"},
          "content": {
              "version": "2022-01-01",
              "elements": [
                  {
                      "type": "text",
                      "content": "This only appears in email and push",
                      "channels": ["email", "push"],
                  }
              ],
          },
      }
  )
  ```
</CodeGroup>

### Realistic Examples

**Channel-specific content:**

```json theme={null}
{
  "version": "2022-01-01",
  "elements": [
    {
      "type": "text",
      "content": "Your order #{{order_number}} has shipped! Track it here: {{tracking_url}}",
      "channels": ["email"]
    },
    {
      "type": "text",
      "content": "Order {{order_number}} shipped. Track: {{tracking_url}}",
      "channels": ["sms"]
    },
    {
      "type": "text",
      "content": "Your order has shipped!",
      "channels": ["push"]
    }
  ]
}
```

**Hide complex elements on SMS:**

```json theme={null}
{
  "type": "columns",
  "channels": ["email"],
  "elements": [
    {
      "type": "column",
      "width": "50%",
      "elements": [
        {
          "type": "image",
          "src": "{{product_image}}"
        }
      ]
    },
    {
      "type": "column",
      "width": "50%",
      "elements": [
        {
          "type": "text",
          "content": "{{product_name}}"
        }
      ]
    }
  ]
}
```

## Combining Control Flow Properties

You can combine multiple control flow properties on the same element:

```json theme={null}
{
  "type": "text",
  "content": "Premium feature available!",
  "if": "data.user_tier === 'premium'",
  "channels": ["email", "push"],
  "ref": "premium_notice"
}
```

**Evaluation order:**

1. `channels` - Element must match current channel
2. `if` - Condition must evaluate to truthy
3. `loop` - Element is repeated for each item (if present)
4. `ref` - Element is registered for reference (if present)

## Best Practices

* **Use `if` for conditional content**: Show/hide elements based on data or user context
* **Use `loop` with `group`**: Wrap looped elements in a group for better organization
* **Reference order matters**: Elements must be defined before they're referenced
* **Test with real data**: Control flow expressions are evaluated at render time, so test with realistic data structures
* **Combine with locales**: Use control flow with [localization](/platform/content/elemental/locales) for fully dynamic, multi-language notifications
* **Channel considerations**: Remember that some elements (like columns) may not render well on all channels

## Related Documentation

<CardGroup cols={2}>
  <Card title="Elemental Overview" icon="file-code" href="/platform/content/elemental/elemental-overview">
    Learn about Courier Elemental and its capabilities.
  </Card>

  <Card title="Elements Reference" icon="book" href="/platform/content/elemental/elements/index">
    Complete reference for all Elemental element types.
  </Card>

  <Card title="Locales" icon="globe" href="/platform/content/elemental/locales">
    Localize your notification content for multiple languages.
  </Card>

  <Card title="Channel Element" icon="code" href="/platform/content/elemental/elements/channel">
    Use Channel elements for advanced channel-specific customization.
  </Card>
</CardGroup>
